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

This commit is contained in:
Adam Wulf
2019-06-25 22:36:13 -05:00
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 dist/*.map
yarn-error.log 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:** **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)) - 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)) - 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. 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 ### Flowchart
@@ -35,7 +52,7 @@ sequenceDiagram
loop Healthcheck loop Healthcheck
John->>John: Fight against hypochondria John->>John: Fight against hypochondria
end end
Note right of John: Rational thoughts <br/>prevail... Note right of John: Rational thoughts <br/>prevail!
John-->>Alice: Great! John-->>Alice: Great!
John->>Bob: How about you? John->>Bob: How about you?
Bob-->>John: Jolly good! Bob-->>John: Jolly good!
@@ -49,6 +66,7 @@ sequenceDiagram
gantt gantt
dateFormat YYYY-MM-DD dateFormat YYYY-MM-DD
title Adding GANTT diagram to mermaid title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
section A section section A section
Completed task :done, des1, 2014-01-06,2014-01-08 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. represent the project when answering questions and issues.
Together we could continue the work with things like: 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 * improving existing diagrams
Don't hesitate to contact me if you want to get involved. 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 a1-->a2
end end
</div> </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"> <div class="mermaid">
graph LR graph LR
456ac9b0d15a8b7f1e71073221059886[1051 AAA fa:fa-check] 456ac9b0d15a8b7f1e71073221059886[1051 AAA fa:fa-check]
@@ -239,6 +268,9 @@ class A someclass;
<div class="mermaid"> <div class="mermaid">
sequenceDiagram sequenceDiagram
participant Alice
participant Bob
participant John as John<br/>Second Line
Alice ->> Bob: Hello Bob, how are you? Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John? Bob-->>John: How about you John?
Bob--x Alice: I am good thanks! Bob--x Alice: I am good thanks!
@@ -266,6 +298,7 @@ gantt
dateFormat YYYY-MM-DD dateFormat YYYY-MM-DD
axisFormat %d/%m axisFormat %d/%m
title Adding GANTT diagram to mermaid title Adding GANTT diagram to mermaid
excludes weekdays 2014-01-10
section A section section A section
Completed task :done, des1, 2014-01-06,2014-01-08 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 gantt diagram to demo page :after a1 , 20h
Add another diagram to demo page :doc1, after a1 , 48h 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 section Last section
Describe gantt syntax :after doc1, 3d Describe gantt syntax :after doc1, 3d
Add gantt diagram to demo page : 20h Add gantt diagram to demo page : 20h
@@ -318,11 +358,11 @@ merge newbranch
<div class="mermaid"> <div class="mermaid">
classDiagram classDiagram
Class01 <|-- AveryLongClass : Cool Class01 <|-- AveryLongClass : Cool
Class03 *-- Class04 Class03 "0" *-- "0..n" Class04
Class05 o-- Class06 Class05 "1" o-- "many" Class06
Class07 .. Class08 Class07 .. Class08
Class09 --> C2 : Where am i? Class09 "many" --> "1" C2 : Where am i?
Class09 --* C3 Class09 "0" --* "1..n" C3
Class09 --|> Class07 Class09 --|> Class07
Class07 : equals() Class07 : equals()
Class07 : Object[] elementData Class07 : Object[] elementData
@@ -344,6 +384,11 @@ Class08 <--> C2: Cool label
}); });
</script> </script>
<script> <script>
function ganttTestClick(a, b, c){
console.log("a:", a)
console.log("b:", b)
console.log("c:", c)
}
function testClick(nodeId) { function testClick(nodeId) {
console.log("clicked", nodeId) console.log("clicked", nodeId)
var originalBgColor = document.querySelector('body').style.backgroundColor 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", "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.", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.js", "main": "dist/mermaid.core.js",
"keywords": [ "keywords": [
@@ -15,12 +15,14 @@
"scripts": { "scripts": {
"build": "webpack --progress --colors", "build": "webpack --progress --colors",
"build:watch": "yarn build --watch", "build:watch": "yarn build --watch",
"minify": "minify ./dist/mermaid.js > ./dist/mermaid.min.js",
"release": "yarn build -p --config webpack.config.prod.babel.js", "release": "yarn build -p --config webpack.config.prod.babel.js",
"upgrade": "yarn-upgrade-all",
"lint": "standard", "lint": "standard",
"test": "yarn lint && jest", "e2e": "yarn lint && jest e2e --config e2e/jest.config.js",
"test:watch": "jest --watch", "dev": "yarn lint && webpack-dev-server --config webpack.config.e2e.js",
"jison": "node -r babel-register node_modules/.bin/gulp jison", "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", "prepublishOnly": "yarn build && yarn release && yarn test",
"prepush": "yarn test" "prepush": "yarn test"
}, },
@@ -34,47 +36,59 @@
"ignore": [ "ignore": [
"**/parser/*.js", "**/parser/*.js",
"dist/**/*.js" "dist/**/*.js"
],
"globals": [
"page"
] ]
}, },
"dependencies": { "dependencies": {
"d3": "^4.13.0", "d3": "^5.7.0",
"dagre-d3-renderer": "^0.5.8", "dagre-d3-renderer": "^0.5.8",
"dagre-layout": "^0.8.8", "dagre-layout": "^0.8.8",
"graphlibrary": "^2.2.0", "graphlibrary": "^2.2.0",
"he": "^1.1.1", "he": "^1.2.0",
"lodash": "^4.17.5", "moment-mini": "^2.22.1",
"moment": "^2.21.0", "lodash": "^4.17.11",
"scope-css": "^1.0.5" "minify": "^4.1.1",
"scope-css": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.0", "@babel/core": "^7.2.2",
"babel-loader": "^7.1.4", "@babel/preset-env": "^7.2.0",
"babel-preset-env": "^1.6.1", "@babel/register": "^7.0.0",
"coveralls": "^3.0.0", "babel-core": "7.0.0-bridge.0",
"css-loader": "^0.28.11", "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", "css-to-string-loader": "^0.1.3",
"gulp": "^3.9.1", "gulp": "^4.0.0",
"gulp-filelog": "^0.4.1", "gulp-filelog": "^0.4.1",
"gulp-jison": "^1.2.0", "gulp-jison": "^1.2.0",
"husky": "^0.14.3", "husky": "^1.2.1",
"identity-obj-proxy": "^3.0.0", "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", "jison": "^0.4.18",
"node-sass": "^4.7.2", "moment": "^2.23.0",
"sass-loader": "^6.0.7", "node-sass": "^4.11.0",
"standard": "^11.0.1", "puppeteer": "^1.17.0",
"webpack": "^4.1.1", "sass-loader": "^7.1.0",
"webpack-cli": "^2.0.12", "standard": "^12.0.1",
"webpack-node-externals": "^1.6.0", "webpack": "^4.27.1",
"yarn-upgrade-all": "^0.3.0" "webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.4.1",
"webpack-node-externals": "^1.7.2",
"yarn-upgrade-all": "^0.5.0"
}, },
"files": [ "files": [
"dist", "dist"
"src"
], ],
"jest": { "yarn-upgrade-all": {
"moduleNameMapper": { "ignore": [
"\\.(css|scss)$": "identity-obj-proxy" "babel-core"
} ]
} }
} }

View File

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

View File

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

View File

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

View File

@@ -77,91 +77,94 @@ var parser = {trace: function trace() { },
yy: {}, 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}, 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"}, 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 */) { performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */ /* this == yyval */
var $0 = $$.length - 1; var $0 = $$.length - 1;
switch (yystate) { switch (yystate) {
case 5: case 6:
this.$=$$[$0-1]+$$[$0]; this.$=$$[$0-1]+$$[$0];
break; break;
case 6: case 7:
this.$=$$[$0]; this.$=$$[$0];
break; break;
case 7: case 8:
yy.addRelation($$[$0]); yy.addRelation($$[$0]);
break; break;
case 8: case 9:
$$[$0-1].title = yy.cleanupLabel($$[$0]); yy.addRelation($$[$0-1]); $$[$0-1].title = yy.cleanupLabel($$[$0]); yy.addRelation($$[$0-1]);
break; break;
case 12: case 12:
/*console.log($$[$0-3],JSON.stringify($$[$0-1]));*/yy.addMembers($$[$0-3],$$[$0-1]); yy.addClass($$[$0]);
break; break;
case 13: case 13:
this.$ = [$$[$0]]; /*console.log($$[$0-3],JSON.stringify($$[$0-1]));*/yy.addClass($$[$0-3]);yy.addMembers($$[$0-3],$$[$0-1]);
break; break;
case 14: case 14:
$$[$0].push($$[$0-1]);this.$=$$[$0]; this.$ = [$$[$0]];
break; break;
case 15: case 15:
/*console.log('Rel found',$$[$0]);*/ $$[$0].push($$[$0-1]);this.$=$$[$0];
break; break;
case 16: case 16:
yy.addMembers($$[$0-1],yy.cleanupLabel($$[$0])); /*console.log('Rel found',$$[$0]);*/
break; break;
case 17: case 17:
console.warn('Member',$$[$0]); yy.addMember($$[$0-1],yy.cleanupLabel($$[$0]));
break; break;
case 18: case 18:
/*console.log('sep found',$$[$0]);*/ console.warn('Member',$$[$0]);
break; break;
case 19: case 19:
this.$ = {'id1':$$[$0-2],'id2':$$[$0], relation:$$[$0-1], relationTitle1:'none', relationTitle2:'none'}; /*console.log('sep found',$$[$0]);*/
break; break;
case 20: 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; break;
case 21: 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; break;
case 22: 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; break;
case 23: 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; break;
case 24: case 24:
this.$={type1:'none',type2:$$[$0],lineType:$$[$0-1]}; this.$={type1:$$[$0-2],type2:$$[$0],lineType:$$[$0-1]};
break; break;
case 25: case 25:
this.$={type1:$$[$0-1],type2:'none',lineType:$$[$0]}; this.$={type1:'none',type2:$$[$0],lineType:$$[$0-1]};
break; break;
case 26: case 26:
this.$={type1:'none',type2:'none',lineType:$$[$0]}; this.$={type1:$$[$0-1],type2:'none',lineType:$$[$0]};
break; break;
case 27: case 27:
this.$=yy.relationType.AGGREGATION; this.$={type1:'none',type2:'none',lineType:$$[$0]};
break; break;
case 28: case 28:
this.$=yy.relationType.EXTENSION; this.$=yy.relationType.AGGREGATION;
break; break;
case 29: case 29:
this.$=yy.relationType.COMPOSITION; this.$=yy.relationType.EXTENSION;
break; break;
case 30: case 30:
this.$=yy.relationType.DEPENDENCY; this.$=yy.relationType.COMPOSITION;
break; break;
case 31: case 31:
this.$=yy.lineType.LINE; this.$=yy.relationType.DEPENDENCY;
break; break;
case 32: case 32:
this.$=yy.lineType.LINE;
break;
case 33:
this.$=yy.lineType.DOTTED_LINE; this.$=yy.lineType.DOTTED_LINE;
break; 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]}], 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,4],49:[2,14]}, defaultActions: {2:[2,1],18:[2,2],34:[2,5],49:[2,15]},
parseError: function parseError(str, hash) { parseError: function parseError(str, hash) {
if (hash.recoverable) { if (hash.recoverable) {
this.trace(str); this.trace(str);

View File

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

View File

@@ -4,6 +4,7 @@ import * as d3 from 'd3'
import flowDb from './flowDb' import flowDb from './flowDb'
import flow from './parser/flow' import flow from './parser/flow'
import dagreD3 from 'dagre-d3-renderer' import dagreD3 from 'dagre-d3-renderer'
import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js'
import { logger } from '../../logger' import { logger } from '../../logger'
import { interpolateToCurve } from '../../utils' import { interpolateToCurve } from '../../utils'
@@ -21,7 +22,8 @@ export const setConf = function (cnf) {
* @param vert Object containing the vertices. * @param vert Object containing the vertices.
* @param g The graph that is to be drawn. * @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 keys = Object.keys(vert)
const styleFromStyleArr = function (styleStr, arr) { const styleFromStyleArr = function (styleStr, arr) {
@@ -35,45 +37,41 @@ export const addVertices = function (vert, g) {
return styleStr 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) { keys.forEach(function (id) {
const vertice = vert[id] const vertex = vert[id]
let verticeText
/** /**
* Variable for storing the classes for the vertice * Variable for storing the classes for the vertex
* @type {string} * @type {string}
*/ */
let classStr = '' let classStr = ''
if (vertice.classes.length > 0) { if (vertex.classes.length > 0) {
classStr = vertice.classes.join(' ') classStr = vertex.classes.join(' ')
} }
/** /**
* Variable for storing the extracted style for the vertice * Variable for storing the extracted style for the vertex
* @type {string} * @type {string}
*/ */
let style = '' let style = ''
// Create a compound style definition from the style definitions found for the node in the graph definition // 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 // Use vertex id as text in the box if no text is provided by the graph definition
if (typeof vertice.text === 'undefined') { let vertexText = vertex.text !== undefined ? vertex.text : vertex.id
verticeText = vertice.id
} else {
verticeText = vertice.text
}
let labelTypeStr = '' // We create a SVG label, either by delegating to addHtmlLabel or manually
let vertexNode
if (conf.htmlLabels) { if (conf.htmlLabels) {
labelTypeStr = 'html' // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
verticeText = verticeText.replace(/fa:fa[\w-]+/g, function (s) { const node = { label: vertexText.replace(/fa[lrsb]?:fa-[\w-]+/g, s => `<i class='${s.replace(':', ' ')}'></i>`) }
return '<i class="fa ' + s.substring(3) + '"></i>' vertexNode = addHtmlLabel(svg, node).node()
}) vertexNode.parentNode.removeChild(vertexNode)
} else { } else {
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text') 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++) { for (let j = 0; j < rows.length; j++) {
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan') 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] tspan.textContent = rows[j]
svgLabel.appendChild(tspan) svgLabel.appendChild(tspan)
} }
vertexNode = svgLabel
}
labelTypeStr = 'svg' // If the node has a link, we wrap it in a SVG link
verticeText = svgLabel 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 radious = 0
let _shape = '' let _shape = ''
// Set the shape based parameters // Set the shape based parameters
switch (vertice.type) { switch (vertex.type) {
case 'round': case 'round':
radious = 5 radious = 5
_shape = 'rect' _shape = 'rect'
@@ -122,14 +127,12 @@ export const addVertices = function (vert, g) {
break break
case 'group': case 'group':
_shape = 'rect' _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 break
default: default:
_shape = 'rect' _shape = 'rect'
} }
// Add the node // 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>' edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>'
} else { } else {
edgeData.labelType = 'text' 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') edgeData.label = edge.text.replace(/<br>/g, '\n')
} }
} else { } else {
@@ -270,7 +273,7 @@ export const draw = function (text, id) {
const subGraphs = flowDb.getSubGraphs() const subGraphs = flowDb.getSubGraphs()
for (let i = subGraphs.length - 1; i >= 0; i--) { for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[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 // 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) g.setParent(subG.nodes[j], subG.id)
} }
} }
addVertices(vert, g) addVertices(vert, g, id)
addEdges(edges, g) addEdges(edges, g)
// Create the renderer // Create the renderer
@@ -464,6 +467,7 @@ export const draw = function (text, id) {
// Index nodes // Index nodes
flowDb.indexNodes('subGraph' + i) flowDb.indexNodes('subGraph' + i)
// reposition labels
for (i = 0; i < subGraphs.length; i++) { for (i = 0; i < subGraphs.length; i++) {
subG = subGraphs[i] subG = subGraphs[i]
@@ -475,19 +479,9 @@ export const draw = function (text, id) {
const yPos = clusterRects[0].y.baseVal.value const yPos = clusterRects[0].y.baseVal.value
const width = clusterRects[0].width.baseVal.value const width = clusterRects[0].width.baseVal.value
const cluster = d3.select(clusterEl[0]) const cluster = d3.select(clusterEl[0])
const te = cluster.append('text') const te = cluster.select('.label')
te.attr('x', xPos + width / 2) te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`)
te.attr('y', yPos + 14)
te.attr('fill', 'black')
te.attr('stroke', 'none')
te.attr('id', id + 'Text') 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 | clickStatement separator
{$$=[];} {$$=[];}
| subgraph text separator document end | subgraph SPACE alphaNum SQS text SQE separator document end
{$$=yy.addSubGraph($4,$2);} {$$=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 | subgraph separator document end
{$$=yy.addSubGraph($3,undefined);} {$$=yy.addSubGraph(undefined,$3,undefined);}
; ;
separator: NEWLINE | SEMI | EOF ; separator: NEWLINE | SEMI | EOF ;
@@ -406,10 +410,10 @@ classStatement:CLASS SPACE alphaNum SPACE alphaNum
; ;
clickStatement clickStatement
: CLICK SPACE alphaNum SPACE alphaNum {$$ = $1;yy.setClickEvent($3, $5, undefined, undefined);} : CLICK SPACE alphaNum SPACE alphaNum {$$ = $1;yy.setClickEvent($3, $5, undefined);}
| CLICK SPACE alphaNum SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, $5, undefined, $7) ;} | CLICK SPACE alphaNum SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, $5, $7) ;}
| CLICK SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, undefined);} | CLICK SPACE alphaNum SPACE STR {$$ = $1;yy.setLink($3, $5, undefined);}
| CLICK SPACE alphaNum SPACE STR SPACE STR {$$ = $1;yy.setClickEvent($3, undefined, $5, $7 );} | CLICK SPACE alphaNum SPACE STR SPACE STR {$$ = $1;yy.setLink($3, $5, $7 );}
; ;
styleStatement:STYLE SPACE alphaNum SPACE stylesOpt styleStatement:STYLE SPACE alphaNum SPACE stylesOpt
@@ -420,21 +424,27 @@ styleStatement:STYLE SPACE alphaNum SPACE stylesOpt
linkStyleStatement linkStyleStatement
: LINKSTYLE SPACE DEFAULT SPACE stylesOpt : LINKSTYLE SPACE DEFAULT SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);} {$$ = $1;yy.updateLink([$3],$5);}
| LINKSTYLE SPACE NUM SPACE stylesOpt | LINKSTYLE SPACE numList SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);} {$$ = $1;yy.updateLink($3,$5);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt | LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);} {$$ = $1;yy.updateLinkInterpolate([$3],$7);yy.updateLink([$3],$9);}
| LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt | LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);} {$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum | LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate($3,$7);} {$$ = $1;yy.updateLinkInterpolate([$3],$7);}
| LINKSTYLE SPACE NUM SPACE INTERPOLATE SPACE alphaNum | LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate($3,$7);} {$$ = $1;yy.updateLinkInterpolate($3,$7);}
; ;
commentStatement: PCT PCT commentText; commentStatement: PCT PCT commentText;
numList: NUM
{$$ = [$1]}
| numList COMMA NUM
{$1.push($3);$$ = $1;}
;
stylesOpt: style stylesOpt: style
{$$ = [$1]} {$$ = [$1]}
| stylesOpt COMMA style | 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[0]).toBe('a1')
expect(subgraph.nodes[1]).toBe('a2') expect(subgraph.nodes[1]).toBe('a2')
expect(subgraph.title).toBe('One') 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 res = flow.parser.parse('graph >;A-->B;')
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices()
@@ -51,7 +76,7 @@ describe('when parsing ', function () {
expect(edges[0].text).toBe('') 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 res = flow.parser.parse('graph <;A-->B;')
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices()
@@ -69,7 +94,7 @@ describe('when parsing ', function () {
expect(edges[0].text).toBe('') 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 res = flow.parser.parse('graph ^;A-->B;')
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices()
@@ -428,6 +453,28 @@ describe('when parsing ', function () {
expect(edges[0].type).toBe('arrow') 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 () { it('should handle line interpolation default definitions', function () {
const res = flow.parser.parse('graph TD\n' + const res = flow.parser.parse('graph TD\n' +
'A-->B\n' + 'A-->B\n' +
@@ -453,6 +500,19 @@ describe('when parsing ', function () {
expect(edges[1].interpolate).toBe('cardinal') 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 () { it('should handle line interpolation default with style', function () {
const res = flow.parser.parse('graph TD\n' + const res = flow.parser.parse('graph TD\n' +
'A-->B\n' + 'A-->B\n' +
@@ -478,6 +538,19 @@ describe('when parsing ', function () {
expect(edges[1].interpolate).toBe('cardinal') 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 () { describe('it should handle interaction, ', function () {
it('it should be possible to use click to a callback', function () { it('it should be possible to use click to a callback', function () {
spyOn(flowDb, 'setClickEvent') spyOn(flowDb, 'setClickEvent')
@@ -486,7 +559,7 @@ describe('when parsing ', function () {
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges() 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 () { 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 vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges() 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 () { 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 res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html"')
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges() 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 () { 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 res = flow.parser.parse('graph TD\nA-->B\nclick A "click.html" "tooltip"')
const vert = flow.parser.yy.getVertices() const vert = flow.parser.yy.getVertices()
const edges = flow.parser.yy.getEdges() 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 { logger } from '../../logger'
import * as d3 from 'd3'
let dateFormat = '' let dateFormat = ''
let axisFormat = '' let axisFormat = ''
let excludes = []
let title = '' let title = ''
let sections = [] let sections = []
let tasks = [] let tasks = []
let currentSection = '' let currentSection = ''
const tags = ['active', 'done', 'crit', 'milestone']
let funs = []
export const clear = function () { export const clear = function () {
sections = [] sections = []
tasks = [] tasks = []
currentSection = '' currentSection = ''
funs = []
title = '' title = ''
taskCnt = 0 taskCnt = 0
lastTask = undefined lastTask = undefined
@@ -31,6 +36,10 @@ export const setDateFormat = function (txt) {
dateFormat = txt dateFormat = txt
} }
export const setExcludes = function (txt) {
excludes = txt.toLowerCase().split(/[\s,]+/)
}
export const setTitle = function (txt) { export const setTitle = function (txt) {
title = txt title = txt
} }
@@ -58,6 +67,42 @@ export const getTasks = function () {
return tasks 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) { const getStartDate = function (prevTime, dateFormat, str) {
str = str.trim() str = str.trim()
@@ -77,8 +122,9 @@ const getStartDate = function (prevTime, dateFormat, str) {
} }
// Check for actual date set // Check for actual date set
if (moment(str, dateFormat.trim(), true).isValid()) { let mDate = moment(str, dateFormat.trim(), true)
return moment(str, dateFormat.trim(), true).toDate() if (mDate.isValid()) {
return mDate.toDate()
} else { } else {
logger.debug('Invalid date:' + str) logger.debug('Invalid date:' + str)
logger.debug('With date format:' + dateFormat.trim()) logger.debug('With date format:' + dateFormat.trim())
@@ -92,8 +138,9 @@ const getEndDate = function (prevTime, dateFormat, str) {
str = str.trim() str = str.trim()
// Check for actual date // Check for actual date
if (moment(str, dateFormat.trim(), true).isValid()) { let mDate = moment(str, dateFormat.trim(), true)
return moment(str, dateFormat.trim()).toDate() if (mDate.isValid()) {
return mDate.toDate()
} }
const d = moment(prevTime) const d = moment(prevTime)
@@ -119,7 +166,6 @@ const getEndDate = function (prevTime, dateFormat, str) {
d.add(durationStatement[1], 'weeks') d.add(durationStatement[1], 'weeks')
break break
} }
return d.toDate()
} }
// Default date - now // Default date - now
return d.toDate() return d.toDate()
@@ -157,49 +203,39 @@ const compileData = function (prevTask, dataStr) {
const task = {} const task = {}
// Get tags like active, done cand crit // Get tags like active, done, crit and milestone
let matchFound = true getTaskTags(data, task, tags)
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
}
}
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
data[i] = data[i].trim() data[i] = data[i].trim()
} }
let endTimeData = ''
switch (data.length) { switch (data.length) {
case 1: case 1:
task.id = parseId() task.id = parseId()
task.startTime = prevTask.endTime task.startTime = prevTask.endTime
task.endTime = getEndDate(task.startTime, dateFormat, data[0]) endTimeData = data[0]
break break
case 2: case 2:
task.id = parseId() task.id = parseId()
task.startTime = getStartDate(undefined, dateFormat, data[0]) task.startTime = getStartDate(undefined, dateFormat, data[0])
task.endTime = getEndDate(task.startTime, dateFormat, data[1]) endTimeData = data[1]
break break
case 3: case 3:
task.id = parseId(data[0]) task.id = parseId(data[0])
task.startTime = getStartDate(undefined, dateFormat, data[1]) task.startTime = getStartDate(undefined, dateFormat, data[1])
task.endTime = getEndDate(task.startTime, dateFormat, data[2]) endTimeData = data[2]
break break
default: 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 return task
} }
@@ -215,26 +251,9 @@ const parseData = function (prevTaskId, dataStr) {
const task = {} const task = {}
// Get tags like active, done cand crit // Get tags like active, done, crit and milestone
let matchFound = true getTaskTags(data, task, tags)
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
}
}
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
data[i] = data[i].trim() data[i] = data[i].trim()
} }
@@ -242,18 +261,33 @@ const parseData = function (prevTaskId, dataStr) {
switch (data.length) { switch (data.length) {
case 1: case 1:
task.id = parseId() task.id = parseId()
task.startTime = { type: 'prevTaskEnd', id: prevTaskId } task.startTime = {
task.endTime = { data: data[0] } type: 'prevTaskEnd',
id: prevTaskId
}
task.endTime = {
data: data[0]
}
break break
case 2: case 2:
task.id = parseId() task.id = parseId()
task.startTime = { type: 'getStartDate', startData: data[0] } task.startTime = {
task.endTime = { data: data[1] } type: 'getStartDate',
startData: data[0]
}
task.endTime = {
data: data[1]
}
break break
case 3: case 3:
task.id = parseId(data[0]) task.id = parseId(data[0])
task.startTime = { type: 'getStartDate', startData: data[1] } task.startTime = {
task.endTime = { data: data[2] } type: 'getStartDate',
startData: data[1]
}
task.endTime = {
data: data[2]
}
break break
default: default:
} }
@@ -270,8 +304,11 @@ export const addTask = function (descr, data) {
section: currentSection, section: currentSection,
type: currentSection, type: currentSection,
processed: false, processed: false,
manualEndTime: false,
renderEndTime: null,
raw: { data: data }, raw: { data: data },
task: descr task: descr,
classes: []
} }
const taskInfo = parseData(lastTaskID, data) const taskInfo = parseData(lastTaskID, data)
rawTask.raw.startTime = taskInfo.startTime rawTask.raw.startTime = taskInfo.startTime
@@ -281,6 +318,7 @@ export const addTask = function (descr, data) {
rawTask.active = taskInfo.active rawTask.active = taskInfo.active
rawTask.done = taskInfo.done rawTask.done = taskInfo.done
rawTask.crit = taskInfo.crit rawTask.crit = taskInfo.crit
rawTask.milestone = taskInfo.milestone
const pos = rawTasks.push(rawTask) const pos = rawTasks.push(rawTask)
@@ -299,7 +337,8 @@ export const addTaskOrg = function (descr, data) {
section: currentSection, section: currentSection,
type: currentSection, type: currentSection,
description: descr, description: descr,
task: descr task: descr,
classes: []
} }
const taskInfo = compileData(lastTask, data) const taskInfo = compileData(lastTask, data)
newTask.startTime = taskInfo.startTime newTask.startTime = taskInfo.startTime
@@ -308,6 +347,7 @@ export const addTaskOrg = function (descr, data) {
newTask.active = taskInfo.active newTask.active = taskInfo.active
newTask.done = taskInfo.done newTask.done = taskInfo.done
newTask.crit = taskInfo.crit newTask.crit = taskInfo.crit
newTask.milestone = taskInfo.milestone
lastTask = newTask lastTask = newTask
tasks.push(newTask) tasks.push(newTask)
} }
@@ -333,6 +373,8 @@ const compileTasks = function () {
rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data) rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data)
if (rawTasks[pos].endTime) { if (rawTasks[pos].endTime) {
rawTasks[pos].processed = true 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 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 { export default {
clear, clear,
setDateFormat, setDateFormat,
@@ -359,5 +503,25 @@ export default {
getTasks, getTasks,
addTask, addTask,
findTaskById, 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 */ /* eslint-env jasmine */
import moment from 'moment' import moment from 'moment-mini'
import ganttDb from './ganttDb' import ganttDb from './ganttDb'
describe('when using the ganttDb', function () { describe('when using the ganttDb', function () {
@@ -7,151 +7,48 @@ describe('when using the ganttDb', function () {
ganttDb.clear() ganttDb.clear()
}) })
it('should handle an fixed dates', function () { it.each`
ganttDb.setDateFormat('YYYY-MM-DD') testName | section | taskName | taskData | expStartDate | expEndDate | expId | expTask
ganttDb.addSection('testa1') ${'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'}
ganttDb.addTask('test1', 'id1,2013-01-01,2013-01-12') ${'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'}
const tasks = ganttDb.getTasks() ${'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'}
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) ${'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'}
expect(tasks[0].endTime).toEqual(moment('2013-01-12', 'YYYY-MM-DD').toDate()) ${'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'}
expect(tasks[0].id).toEqual('id1') ${'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'}
expect(tasks[0].task).toEqual('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'}
it('should handle duration (days) instead of fixed date to determine end date', function () { `('$testName', ({ section, taskName, taskData, expStartDate, expEndDate, expId, expTask }) => {
ganttDb.setDateFormat('YYYY-MM-DD') ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1') ganttDb.addSection(section)
ganttDb.addTask('test1', 'id1,2013-01-01,2d') ganttDb.addTask(taskName, taskData)
const tasks = ganttDb.getTasks() const tasks = ganttDb.getTasks()
expect(tasks[0].startTime).toEqual(moment('2013-01-01', 'YYYY-MM-DD').toDate()) expect(tasks[0].startTime).toEqual(expStartDate)
expect(tasks[0].endTime).toEqual(moment('2013-01-03', 'YYYY-MM-DD').toDate()) expect(tasks[0].endTime).toEqual(expEndDate)
expect(tasks[0].id).toEqual('id1') expect(tasks[0].id).toEqual(expId)
expect(tasks[0].task).toEqual('test1') expect(tasks[0].task).toEqual(expTask)
}) })
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('should handle relative start date based on id', function () { it.each`
ganttDb.setDateFormat('YYYY-MM-DD') section | taskName1 | taskName2 | taskData1 | taskData2 | expStartDate2 | expEndDate2 | expId2 | expTask2
ganttDb.addSection('testa1') ${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'id2'} | ${'test2'}
ganttDb.addTask('test1', 'id1,2013-01-01,2w') ${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id3,1d'} | ${new Date((new Date()).setHours(0, 0, 0, 0))} | ${undefined} | ${'id2'} | ${'test2'}
ganttDb.addTask('test2', 'id2,after id1,1d') ${'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 () { it('should handle relative start date based on id regardless of sections', function () {
ganttDb.setDateFormat('YYYY-MM-DD') ganttDb.setDateFormat('YYYY-MM-DD')
ganttDb.addSection('testa1') ganttDb.addSection('testa1')
@@ -162,14 +59,70 @@ describe('when using the ganttDb', function () {
const tasks = ganttDb.getTasks() const tasks = ganttDb.getTasks()
expect(tasks[1].startTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate()) expect(tasks[1].startTime).toEqual(new Date(2013, 0, 17))
expect(tasks[1].endTime).toEqual(moment('2013-01-18', 'YYYY-MM-DD').toDate()) expect(tasks[1].endTime).toEqual(new Date(2013, 0, 18))
expect(tasks[1].id).toEqual('id2') expect(tasks[1].id).toEqual('id2')
expect(tasks[1].task).toEqual('test2') expect(tasks[1].task).toEqual('test2')
expect(tasks[2].id).toEqual('id3') expect(tasks[2].id).toEqual('id3')
expect(tasks[2].task).toEqual('test3') expect(tasks[2].task).toEqual('test3')
expect(tasks[2].startTime).toEqual(moment('2013-01-15', 'YYYY-MM-DD').toDate()) expect(tasks[2].startTime).toEqual(new Date(2013, 0, 15))
expect(tasks[2].endTime).toEqual(moment('2013-01-17', 'YYYY-MM-DD').toDate()) 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) { 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') svg.append('g')
.selectAll('rect') .selectAll('rect')
.data(theArray) .data(theArray)
@@ -120,26 +121,42 @@ export const draw = function (text, id) {
return 'section section0' return 'section section0'
}) })
// Draw the rects representing the tasks
const rectangles = svg.append('g') const rectangles = svg.append('g')
.selectAll('rect') .selectAll('rect')
.data(theArray) .data(theArray)
.enter() .enter()
rectangles.append('rect') rectangles.append('rect')
.attr('id', function (d) { return d.id })
.attr('rx', 3) .attr('rx', 3)
.attr('ry', 3) .attr('ry', 3)
.attr('x', function (d) { .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 return timeScale(d.startTime) + theSidePad
}) })
.attr('y', function (d, i) { .attr('y', function (d, i) {
return i * theGap + theTopPad return i * theGap + theTopPad
}) })
.attr('width', function (d) { .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('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) { .attr('class', function (d) {
const res = 'task ' const res = 'task'
let classStr = ''
if (d.classes.length > 0) {
classStr = d.classes.join(' ')
}
let secNum = 0 let secNum = 0
for (let i = 0; i < categories.length; i++) { 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.active) {
if (d.crit) { if (d.crit) {
return res + ' activeCrit' + secNum taskClass += ' activeCrit'
} else { } else {
return res + ' active' + secNum taskClass = ' active'
} }
} } else if (d.done) {
if (d.done) {
if (d.crit) { if (d.crit) {
return res + ' doneCrit' + secNum taskClass = ' doneCrit'
} else { } else {
return res + ' done' + secNum taskClass = ' done'
}
} else {
if (d.crit) {
taskClass += ' crit'
} }
} }
if (d.crit) { if (taskClass.length === 0) {
return res + ' crit' + secNum 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') rectangles.append('text')
.text(function (d) { .text(function (d) {
return d.task return d.task
}) })
.attr('font-size', conf.fontSize) .attr('font-size', conf.fontSize)
.attr('x', function (d) { .attr('x', function (d) {
const startX = timeScale(d.startTime) let startX = timeScale(d.startTime)
const endX = timeScale(d.endTime) 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 const textWidth = this.getBBox().width
// Check id text width > width of rectangle // Check id text width > width of rectangle
@@ -198,8 +233,17 @@ export const draw = function (text, id) {
.attr('text-height', theBarHeight) .attr('text-height', theBarHeight)
.attr('class', function (d) { .attr('class', function (d) {
const startX = timeScale(d.startTime) 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 const textWidth = this.getBBox().width
let classStr = ''
if (d.classes.length > 0) {
classStr = d.classes.join(' ')
}
let secNum = 0 let secNum = 0
for (let i = 0; i < categories.length; i++) { for (let i = 0; i < categories.length; i++) {
if (d.type === categories[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 // Check id text width > width of rectangle
if (textWidth > (endX - startX)) { if (textWidth > (endX - startX)) {
if (endX + textWidth + 1.5 * conf.leftPadding > w) { if (endX + textWidth + 1.5 * conf.leftPadding > w) {
return 'taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType
} else { } else {
return 'taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType
} }
} else { } else {
return 'taskText taskText' + secNum + ' ' + taskType return classStr + ' taskText taskText' + secNum + ' ' + taskType
} }
}) })
} }

View File

@@ -7,27 +7,64 @@
%options case-insensitive %options case-insensitive
%{ %x click
// Pre-lexer code can go here %x href
%} %x callbackname
%x callbackargs
%% %%
[\n]+ return 'NL'; [\n]+ return 'NL';
\s+ /* skip whitespace */ \s+ /* skip whitespace */
\#[^\n]* /* skip comments */ \#[^\n]* /* skip comments */
\%%[^\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'; "dateFormat"\s[^#\n;]+ return 'dateFormat';
"axisFormat"\s[^#\n;]+ return 'axisFormat'; "axisFormat"\s[^#\n;]+ return 'axisFormat';
"excludes"\s[^#\n;]+ return 'excludes';
\d\d\d\d"-"\d\d"-"\d\d return 'date'; \d\d\d\d"-"\d\d"-"\d\d return 'date';
"title"\s[^#\n;]+ return 'title'; "title"\s[^#\n;]+ return 'title';
"section"\s[^#:\n;]+ return 'section'; "section"\s[^#:\n;]+ return 'section';
[^#:\n;]+ return 'taskTxt'; [^#:\n;]+ return 'taskTxt';
":"[^#\n;]+ return 'taskData'; ":"[^#\n;]+ return 'taskData';
":" return ':'; ":" return ':';
<<EOF>> return 'EOF'; <<EOF>> return 'EOF';
. return 'INVALID'; . return 'INVALID';
/lex /lex
@@ -54,11 +91,40 @@ line
; ;
statement 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);} | 'axisFormat' {yy.setAxisFormat($1.substr(11));$$=$1.substr(11);}
| title {yy.setTitle($1.substr(6));$$=$1.substr(6);} | 'excludes' {yy.setExcludes($1.substr(9));$$=$1.substr(9);}
| section {yy.addSection($1.substr(8));$$=$1.substr(8);} | title {yy.setTitle($1.substr(6));$$=$1.substr(6);}
| taskTxt taskData {yy.addTask($1,$2);$$='task';} | 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 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() { }, var parser = {trace: function trace() { },
yy: {}, 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}, 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:"title",14:"section",15:"taskTxt",16:"taskData"}, 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,2]], 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 */) { performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */ /* this == yyval */
@@ -105,17 +105,50 @@ case 9:
yy.setAxisFormat($$[$0].substr(11));this.$=$$[$0].substr(11); yy.setAxisFormat($$[$0].substr(11));this.$=$$[$0].substr(11);
break; break;
case 10: case 10:
yy.setTitle($$[$0].substr(6));this.$=$$[$0].substr(6); yy.setExcludes($$[$0].substr(9));this.$=$$[$0].substr(9);
break; break;
case 11: case 11:
yy.addSection($$[$0].substr(8));this.$=$$[$0].substr(8); yy.setTitle($$[$0].substr(6));this.$=$$[$0].substr(6);
break; break;
case 12: case 12:
yy.addSection($$[$0].substr(8));this.$=$$[$0].substr(8);
break;
case 14:
yy.addTask($$[$0-1],$$[$0]);this.$='task'; yy.addTask($$[$0-1],$$[$0]);this.$='task';
break; 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: {}, defaultActions: {},
parseError: function parseError(str, hash) { parseError: function parseError(str, hash) {
if (hash.recoverable) { if (hash.recoverable) {
@@ -593,8 +626,6 @@ stateStackSize:function stateStackSize() {
}, },
options: {"case-insensitive":true}, options: {"case-insensitive":true},
performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
// Pre-lexer code can go here
var YYSTATE=YY_START; var YYSTATE=YY_START;
switch($avoiding_name_collisions) { switch($avoiding_name_collisions) {
case 0:return 10; case 0:return 10;
@@ -605,32 +636,58 @@ case 2:/* skip comments */
break; break;
case 3:/* skip comments */ case 3:/* skip comments */
break; break;
case 4:return 4; case 4:this.begin("href");
break; break;
case 5:return 11; case 5:this.popState();
break; break;
case 6:return 12; case 6:return 22;
break; break;
case 7:return 'date'; case 7:this.begin("callbackname");
break; break;
case 8:return 13; case 8:this.popState();
break; break;
case 9:return 14; case 9:this.popState(); this.begin("callbackargs");
break; break;
case 10:return 15; case 10:return 20;
break; break;
case 11:return 16; case 11:this.popState();
break; break;
case 12:return ':'; case 12:return 21;
break; break;
case 13:return 6; case 13:this.begin("click");
break; 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; 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], 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: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"inclusive":true}} 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; 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] const label = [line, commit.id, commit.seq]
_.each(branches, function (value, key) { for (let branch in branches) {
if (value === commit.id) label.push(key) if (branches[branch] === commit.id) label.push(branch)
}) }
logger.debug(label.join(' ')) logger.debug(label.join(' '))
if (Array.isArray(commit.parent)) { if (Array.isArray(commit.parent)) {
const newCommit = commits[commit.parent[0]] const newCommit = commits[commit.parent[0]]
@@ -188,9 +188,10 @@ export const clear = function () {
} }
export const getBranchesAsObjArray = function () { export const getBranchesAsObjArray = function () {
const branchArr = _.map(branches, function (value, key) { const branchArr = []
return { 'name': key, 'commit': commits[value] } for (let branch in branches) {
}) branchArr.push({ name: branch, commit: commits[branches[branch]] })
}
return branchArr return branchArr
} }

View File

@@ -1,5 +1,5 @@
import _ from 'lodash'
import * as d3 from 'd3' import * as d3 from 'd3'
import _ from 'lodash'
import db from './gitGraphAst' import db from './gitGraphAst'
import gitGraphParser from './parser/gitGraph' import gitGraphParser from './parser/gitGraph'
@@ -160,7 +160,7 @@ function cloneNode (svg, selector) {
function renderCommitHistory (svg, commitid, branches, direction) { function renderCommitHistory (svg, commitid, branches, direction) {
let commit let commit
const numCommits = Object.keys(allCommitsDict).length const numCommits = Object.keys(allCommitsDict).length
if (_.isString(commitid)) { if (typeof commitid === 'string') {
do { do {
commit = allCommitsDict[commitid] commit = allCommitsDict[commitid]
logger.debug('in renderCommitHistory', commit.id, commit.seq) logger.debug('in renderCommitHistory', commit.id, commit.seq)
@@ -189,7 +189,13 @@ function renderCommitHistory (svg, commitid, branches, direction) {
.attr('stroke', config.nodeStrokeColor) .attr('stroke', config.nodeStrokeColor)
.attr('stroke-width', config.nodeStrokeWidth) .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) { if (branch) {
logger.debug('found branch ', branch.name) logger.debug('found branch ', branch.name)
svg.select('#node-' + commit.id + ' p') svg.select('#node-' + commit.id + ' p')
@@ -211,7 +217,7 @@ function renderCommitHistory (svg, commitid, branches, direction) {
} while (commitid && allCommitsDict[commitid]) } while (commitid && allCommitsDict[commitid])
} }
if (_.isArray(commitid)) { if (Array.isArray(commitid)) {
logger.debug('found merge commmit', commitid) logger.debug('found merge commmit', commitid)
renderCommitHistory(svg, commitid[0], branches, direction) renderCommitHistory(svg, commitid[0], branches, direction)
branchNum++ branchNum++
@@ -223,11 +229,11 @@ function renderCommitHistory (svg, commitid, branches, direction) {
function renderLines (svg, commit, direction, branchColor) { function renderLines (svg, commit, direction, branchColor) {
branchColor = branchColor || 0 branchColor = branchColor || 0
while (commit.seq > 0 && !commit.lineDrawn) { while (commit.seq > 0 && !commit.lineDrawn) {
if (_.isString(commit.parent)) { if (typeof commit.parent === 'string') {
svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor) svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor)
commit.lineDrawn = true commit.lineDrawn = true
commit = allCommitsDict[commit.parent] 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[0], direction, branchColor)
svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1) svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1)
renderLines(svg, allCommitsDict[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 // Parse the graph definition
parser.parse(txt + '\n') parser.parse(txt + '\n')
config = _.extend(config, apiConfig, db.getOptions()) config = _.assign(config, apiConfig, db.getOptions())
logger.debug('effective options', config) logger.debug('effective options', config)
const direction = db.getDirection() const direction = db.getDirection()
allCommitsDict = db.getCommits() allCommitsDict = db.getCommits()
@@ -259,11 +265,12 @@ export const draw = function (txt, id, ver) {
const svg = d3.select(`[id="${id}"]`) const svg = d3.select(`[id="${id}"]`)
svgCreateDefs(svg) svgCreateDefs(svg)
branchNum = 1 branchNum = 1
_.each(branches, function (v) { for (let branch in branches) {
const v = branches[branch]
renderCommitHistory(svg, v.commit.id, branches, direction) renderCommitHistory(svg, v.commit.id, branches, direction)
renderLines(svg, v.commit, direction) renderLines(svg, v.commit, direction)
branchNum++ branchNum++
}) }
svg.attr('height', function () { svg.attr('height', function () {
if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing
return (branches.length + 1) * config.branchOffset 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, width: 150,
// Height of actor boxes // Height of actor boxes
height: 65, height: 65,
actorFontSize: 14,
actorFontFamily: '"Open-Sans", "sans-serif"',
// Margin around loop boxes // Margin around loop boxes
boxMargin: 10, boxMargin: 10,
boxTextMargin: 5, boxTextMargin: 5,
@@ -218,9 +220,13 @@ const drawMessage = function (elem, startx, stopx, verticalPos, msg) {
let line let line
if (startx === stopx) { if (startx === stopx) {
line = g.append('path') if (conf.rightAngles) {
.attr('d', 'M ' + startx + ',' + verticalPos + ' C ' + (startx + 60) + ',' + (verticalPos - 10) + ' ' + (startx + 60) + ',' + line = g.append('path').attr('d', `M ${startx},${verticalPos} H ${startx + (conf.width / 2)} V ${verticalPos + 25} H ${startx}`)
(verticalPos + 30) + ' ' + startx + ',' + (verticalPos + 20)) } 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) bounds.bumpVerticalPos(30)
const dx = Math.max(textWidth / 2, 100) const dx = Math.max(textWidth / 2, 100)
@@ -338,7 +344,7 @@ export const draw = function (text, id) {
activationData.starty = verticalPos - 6 activationData.starty = verticalPos - 6
verticalPos += 12 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) 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) drawRect(g, rect)
_drawTextCandidateFunc(conf)(description, g, _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) { export const anchorElement = function (elem) {
@@ -101,12 +101,12 @@ export const anchorElement = function (elem) {
* @param bounds - activation box bounds * @param bounds - activation box bounds
* @param verticalPos - precise y cooridnate of bottom activation box edge * @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 rect = getNoteRect()
const g = bounds.anchored const g = bounds.anchored
rect.x = bounds.startx rect.x = bounds.startx
rect.y = bounds.starty 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.width = bounds.stopx - bounds.startx
rect.height = verticalPos - bounds.starty rect.height = verticalPos - bounds.starty
drawRect(g, rect) drawRect(g, rect)
@@ -216,7 +216,7 @@ export const getTextObj = function () {
const txt = { const txt = {
x: 0, x: 0,
y: 0, y: 0,
'fill': 'black', 'fill': undefined,
'text-anchor': 'start', 'text-anchor': 'start',
style: '#666', style: '#666',
width: 100, width: 100,
@@ -252,22 +252,30 @@ const _drawTextCandidateFunc = (function () {
_setTextAttrs(text, textAttrs) _setTextAttrs(text, textAttrs)
} }
function byTspan (content, g, x, y, width, height, textAttrs) { function byTspan (content, g, x, y, width, height, textAttrs, conf) {
const text = g.append('text') const { actorFontSize, actorFontFamily } = conf
.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)
text.attr('y', y + height / 2.0) const lines = content.split(/<br\/?>/ig)
.attr('dominant-baseline', 'central') for (let i = 0; i < lines.length; i++) {
.attr('alignment-baseline', 'central') 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 s = g.append('switch')
const f = s.append('foreignObject') const f = s.append('foreignObject')
.attr('x', x).attr('y', y) .attr('x', x).attr('y', y)
@@ -280,7 +288,7 @@ const _drawTextCandidateFunc = (function () {
.style('text-align', 'center').style('vertical-align', 'middle') .style('text-align', 'center').style('vertical-align', 'middle')
.text(content) .text(content)
byTspan(content, s, x, y, width, height, textAttrs) byTspan(content, s, x, y, width, height, textAttrs, conf)
_setTextAttrs(text, textAttrs) _setTextAttrs(text, textAttrs)
} }

View File

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

View File

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

View File

@@ -13,6 +13,7 @@
*/ */
import * as d3 from 'd3' import * as d3 from 'd3'
import scope from 'scope-css' import scope from 'scope-css'
import pkg from '../package.json'
import { logger, setLogLevel } from './logger' import { logger, setLogLevel } from './logger'
import utils from './utils' import utils from './utils'
@@ -31,6 +32,9 @@ import classDb from './diagrams/class/classDb'
import gitGraphRenderer from './diagrams/git/gitGraphRenderer' import gitGraphRenderer from './diagrams/git/gitGraphRenderer'
import gitGraphParser from './diagrams/git/parser/gitGraph' import gitGraphParser from './diagrams/git/parser/gitGraph'
import gitGraphAst from './diagrams/git/gitGraphAst' 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 = {} const themes = {}
for (const themeName of ['default', 'forest', 'dark', 'neutral']) { for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
@@ -49,6 +53,17 @@ for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
* ``` * ```
*/ */
const config = { 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', theme: 'default',
themeCSS: undefined, 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 * **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 * 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 /** ### gantt
@@ -220,6 +240,7 @@ function parse (text) {
const graphType = utils.detectType(text) const graphType = utils.detectType(text)
let parser let parser
logger.debug('Type ' + graphType)
switch (graphType) { switch (graphType) {
case 'git': case 'git':
parser = gitGraphParser parser = gitGraphParser
@@ -241,6 +262,11 @@ function parse (text) {
parser = classParser parser = classParser
parser.parser.yy = classDb parser.parser.yy = classDb
break break
case 'info':
logger.debug('info info info')
parser = infoParser
parser.parser.yy = infoDb
break
} }
parser.parser.yy.parseError = (str, hash) => { parser.parser.yy.parseError = (str, hash) => {
@@ -413,6 +439,11 @@ const render = function (id, txt, cb, container) {
classRenderer.setConf(config.class) classRenderer.setConf(config.class)
classRenderer.draw(txt, id) classRenderer.draw(txt, id)
break 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') 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') { if (typeof cb !== 'undefined') {
cb(svgCode, flowDb.bindFunctions) cb(svgCode, flowDb.bindFunctions)
cb(svgCode, ganttDb.bindFunctions)
} else { } else {
logger.warn('CB = undefined!') logger.warn('CB = undefined!')
} }
@@ -465,7 +497,7 @@ const setConf = function (cnf) {
} }
function initialize (options) { function initialize (options) {
logger.debug('Initializing mermaidAPI') logger.debug('Initializing mermaidAPI ', pkg.version)
// Update default config with options supplied at initialization // Update default config with options supplied at initialization
if (typeof options === 'object') { if (typeof options === 'object') {
setConf(options) setConf(options)

View File

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

View File

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

View File

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

View File

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

View File

@@ -66,7 +66,6 @@
/* Task styling */ /* Task styling */
/* Default task */ /* Default task */
.task { .task {
@@ -90,6 +89,27 @@
font-size: 11px; 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*/ /* Specific task settings for the sections*/
@@ -109,7 +129,7 @@
} }
.taskTextOutside0, .taskTextOutside0,
.taskTextOutside2, .taskTextOutside2
{ {
fill: $taskTextOutsideColor; fill: $taskTextOutsideColor;
} }
@@ -188,6 +208,13 @@
shape-rendering: crispEdges; shape-rendering: crispEdges;
} }
.milestone {
transform: rotate(45deg) scale(0.8,0.8);
}
.milestoneText {
font-style: italic;
}
.doneCritText0, .doneCritText0,
.doneCritText1, .doneCritText1,
.doneCritText2, .doneCritText2,

View File

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

View File

@@ -15,7 +15,6 @@ text.actor {
.messageLine0 { .messageLine0 {
stroke-width: 1.5; stroke-width: 1.5;
stroke-dasharray: '2 2'; stroke-dasharray: '2 2';
marker-end: 'url(#arrowhead)';
stroke: $signalColor; stroke: $signalColor;
} }
@@ -50,14 +49,13 @@ text.actor {
} }
.loopText { .loopText {
fill: $labelTextColor; fill: $loopTextColor;
stroke: none; stroke: none;
} }
.loopLine { .loopLine {
stroke-width: 2; stroke-width: 2;
stroke-dasharray: '2 2'; stroke-dasharray: '2 2';
marker-end: 'url(#arrowhead)';
stroke: $labelBoxBorderColor; stroke: $labelBoxBorderColor;
} }
@@ -73,3 +71,18 @@ text.actor {
font-family: 'trebuchet ms', verdana, arial; font-family: 'trebuchet ms', verdana, arial;
font-size: 14px; 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 * as d3 from 'd3'
import { logger } from './logger'
/** /**
* @function detectType * @function detectType
@@ -19,6 +20,7 @@ import * as d3 from 'd3'
*/ */
export const detectType = function (text) { export const detectType = function (text) {
text = text.replace(/^\s*%%.*\n/g, '\n') text = text.replace(/^\s*%%.*\n/g, '\n')
logger.debug('Detecting diagram type based on the text ' + text)
if (text.match(/^\s*sequenceDiagram/)) { if (text.match(/^\s*sequenceDiagram/)) {
return 'sequence' return 'sequence'
} }
@@ -34,6 +36,11 @@ export const detectType = function (text) {
if (text.match(/^\s*gitGraph/)) { if (text.match(/^\s*gitGraph/)) {
return 'git' return 'git'
} }
if (text.match(/^\s*info/)) {
return 'info'
}
return 'flowchart' 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 = { const jsRule = {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, include: [
path.resolve(__dirname, './src'),
path.resolve(__dirname, './node_modules/dagre-d3-renderer/lib')
],
use: { use: {
loader: 'babel-loader' 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