Merge branch 'master' into text-labels

Conflicts:
	dist/mermaid.full.js
	dist/mermaid.full.min.js
	dist/mermaid.slim.js
	dist/mermaid.slim.min.js
	src/diagrams/flowchart/flowRenderer.js
	src/utils.js
	test/web_style.html
This commit is contained in:
Björn Weström 2015-01-13 21:50:05 +01:00
commit 9f970ed953
33 changed files with 3516 additions and 659 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ bower_components/
*.sublime-project
*.sublime-workspace
.DS_Store
.idea
coverage

View File

@ -2,7 +2,7 @@
Great that you want to be involved in this project! Contributing is fun and contributions are GREAT! :)
This page is currently a starting point is not so rigorous to start with.
This page is currently a starting point and is not so rigorous to start with.
Some important guidlines:
@ -16,7 +16,7 @@ The issue list and the items marked with **help wanted** is a good starting poin
## Guidelines for avoiding duplicate work
Contributing is great. It is not so fun when you are done with your issue and just before you're about to push your
change you cant because someone else just pushed the same fix so you have wasted your time. The guidelines below are in
change you can't because someone else just pushed the same fix so you have wasted your time. The guidelines below are in
place to prevent this:
* Comment in the issue that you are working on it. You will then be added as an assignee (eventually).

View File

@ -1,3 +1,4 @@
mermaid [![Build Status](https://travis-ci.org/knsv/mermaid.svg?branch=master)](https://travis-ci.org/knsv/mermaid) [![Code Climate](https://codeclimate.com/github/knsv/mermaid/badges/gpa.svg)](https://codeclimate.com/github/knsv/mermaid)
=======
@ -8,41 +9,61 @@ 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.
The code below would render the following image
```
<table>
<tr><th>Code</th><th>Rendered diagram</th></tr>
<tr><td>
<pre>
<code>
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
would render this lovely chart:
![Example 1](http://www.sveido.com/mermaid/img/ex1.png)
A page with a live example can be seen [here](http://www.sveido.com/mermaid/demo/html/web.html). You can also look at mermaid in action using [jsbin](http://jsbin.com/faxunexeku/1/edit?html,output). If you want a live demo, there is an editor provided in the mermaid project or you can simply look at this [great editor](http://danielmschmidt.github.io/mermaid-demo/)
# [The main documentation is located in the wiki](https://github.com/knsv/mermaid/wiki)
# Another graph example
```
graph LR;
A[Hard edge]-->|Link text|B(Round edge);
B-->C{Decision};
C-->|One|D[Result one];
C-->|Two|E[Result two];
```
![Example 2](http://www.sveido.com/mermaid/img/ex2.png)
<code>
</pre>
</td>
<td>
<img src='http://www.sveido.com/mermaid/img/ex1.png' alt='Example 1'>
</td>
</tr>
<tr>
<td>
<pre>
<code>
sequenceDiagram
participant Alice
participant Bob
Alice->John: Hello John, how are you?
loop Healthcheck
John->John: Fight against hypochondria
end
Note right of John: Rational thoughts &lt;br/>prevail...
John-->Alice: Great!
John->Bob: How about you?
Bob-->John: Jolly good!
</code>
</pre>
</td>
<td>
<img src='http://www.sveido.com/mermaid/img/seq1.png' alt='Example 2'>
</td>
</tr>
</table>
## Further reading
* [Usage](http://knsv.github.io/mermaid/usage.html)
* [Flowchart syntax](http://knsv.github.io/mermaid/flowchart.html)
* [Sequence diagram syntax](http://knsv.github.io/mermaid/sequenceDiagram.html)
* [Mermaid client](http://knsv.github.io/mermaid/mermaidCLI.html)
* [Demos](http://knsv.github.io/mermaid/demos.html)
# Credits
Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries! Thanks also to the [js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams.
Many thanks to the [d3](http://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing
the graphical layout and drawing libraries! Thanks also to the
[js-sequence-diagram](http://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the
sequence diagrams.
*Mermaid was created by Knut Sveidqvist for easier documentation.*
Knut has not done all work by him self, here is the full list of the projects [contributors](https://github.com/knsv/mermaid/graphs/contributors).

27
bin/mermaid.js Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env node
var fs = require('fs')
, chalk = require('chalk')
, error = chalk.bold.red
, cli = require('../lib/cli.js')
, lib = require('../lib')
cli.parse(process.argv.slice(2), function(err, message, options) {
if (err) {
console.error(
error('\nYou had errors in your syntax. Use --help for further information.')
)
err.forEach(function (e) {
console.error(e.message)
})
return
}
else if (message) {
console.log(message)
return
}
lib.process(options.files, options)
})

View File

@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "0.2.15",
"version": "0.3.2",
"authors": [
"knsv <knut@sveido.com>"
],

View File

@ -27,7 +27,7 @@ gulp.task('jison', shell.task([
'jison src/diagrams/flowchart/parser/flow.jison -o src/diagrams/flowchart/parser/flow.js',
'jison src/diagrams/flowchart/parser/dot.jison -o src/diagrams/flowchart/parser/dot.js',
'jison src/diagrams/sequenceDiagram/parser/sequenceDiagram.jison -o src/diagrams/sequenceDiagram/parser/sequenceDiagram.js',
//'jison src/diagrams/sequenceDiagram/parser/sequence.jison -o src/diagrams/sequenceDiagram/parser/sequence.js'
//'jison src/diagrams/sequenceDiagram/parser/sequenceDiagram.jison -o src/diagrams/sequenceDiagram/parser/sequenceDiagram.js'
]));
gulp.task('jison2', function() {
@ -37,6 +37,7 @@ gulp.task('jison2', function() {
});
gulp.task('dist', ['slimDist', 'fullDist','jasmine']);
gulp.task('rdist', ['slimDist', 'fullDist']);
var jasmine = require('gulp-jasmine');
@ -45,6 +46,8 @@ gulp.task('jasmine',['jison','lint'], function () {
.pipe(jasmine({includeStackTrace:true}));
});
gulp.task('tape', shell.task(['./node_modules/.bin/tape ./test/cli_test-*.js']));
gulp.task('coverage', function (cb) {
gulp.src(['src/**/*.js', '!src/**/*.spec.js'])
.pipe(istanbul()) // Covering files
@ -175,3 +178,5 @@ gulp.task('lint', function() {
.pipe(jshint())
.pipe(jshint.reporter(stylish));
});
gulp.task('test',['coverage','tape']);

157
lib/cli.js Normal file
View File

@ -0,0 +1,157 @@
var fs = require('fs')
, exec = require('child_process').exec
, chalk = require('chalk')
, which = require('which')
, parseArgs = require('minimist')
, semver = require('semver')
var PHANTOM_VERSION = "^1.9.0"
var info = chalk.blue.bold
, note = chalk.green.bold
module.exports = function() {
return new cli()
}()
function cli(options) {
this.options = {
alias: {
help: 'h'
, png: 'p'
, outputDir: 'o'
, svg: 's'
, verbose: 'v'
, phantomPath: 'e'
}
, 'boolean': ['help', 'png', 'svg']
, 'string': ['outputDir']
}
this.errors = []
this.message = null
this.helpMessage = [
, info('Usage: mermaid [options] <file>...')
, ""
, "file The mermaid description file to be rendered"
, ""
, "Options:"
, " -s --svg Output SVG instead of PNG (experimental)"
, " -p --png If SVG was selected, and you also want PNG, set this flag"
, " -o --outputDir Directory to save files, will be created automatically, defaults to `cwd`"
, " -e --phantomPath Specify the path to the phantomjs executable"
, " -h --help Show this message"
, " -v --verbose Show logging"
, " --version Print version and quit"
]
return this
}
cli.prototype.parse = function(argv, next) {
var options = parseArgs(argv, this.options)
, phantom
if (options.version) {
var pkg = require('../package.json')
this.message = "" + pkg.version
next(null, this.message)
}
else if (options.help) {
this.message = this.helpMessage.join('\n')
next(null, this.message)
}
else {
options.files = options._
if (!options.files.length) {
this.errors.push(new Error("You must specify at least one source file."))
}
// ensure that parameter-expecting options have parameters
;['outputDir', 'phantomPath'].forEach(function(i) {
if(typeof options[i] !== 'undefined') {
if (typeof options[i] !== 'string' || options[i].length < 1) {
this.errors.push(new Error(i + " expects a value."))
}
}
}.bind(this))
// set svg/png flags appropriately
if (options.svg && !options.png) {
options.png = false
}
else {
options.png = true
}
this.checkPhantom = createCheckPhantom(options.phantomPath)
this.checkPhantom(function(err, path) {
if(err) {
this.errors.push(err)
}
options.phantomPath = path
next(
this.errors.length > 0 ? this.errors : null
, this.message
, options
)
}.bind(this))
}
}
function createCheckPhantom(_phantomPath) {
var phantomPath = _phantomPath
, phantomVersion
return function checkPhantom(_next) {
var next = _next || function() {}
, err
if (typeof phantomPath === 'undefined') {
try {
var phantom = require('phantomjs')
phantomPath = phantom.path
} catch (e) {
try {
phantomPath = which.sync('phantomjs')
} catch (e) {
if (!phantomPath) {
phantomPath = null
err = new Error(
[
"Cannot find phantomjs in your PATH. If phantomjs is installed"
, "you may need to specify its path manually with the '-e' option."
, "Run this executable with '--help' or view the README for more"
, "details."
].join('\n')
)
next(err)
return
}
}
}
}
// If we have phantompath, see if its version satisfies our requirements
exec(phantomPath + ' --version', function(err, stdout, stderr) {
if (err) {
next(new Error("Could not find phantomjs at the specified path."))
}
else if (!semver.satisfies(stdout, PHANTOM_VERSION)) {
next(new Error(
'mermaid requires phantomjs '
+ PHANTOM_VERSION
+ ' to be installed, found version '
+ stdout
))
}
else {
next(null, phantomPath)
}
})
}
}

40
lib/index.js Normal file
View File

@ -0,0 +1,40 @@
var os = require('os')
, fs = require('fs')
, path = require('path')
, spawn = require('child_process').spawn
var mkdirp = require('mkdirp')
var phantomscript = path.join(__dirname, 'phantomscript.js')
module.exports = { process: processMermaid }
function processMermaid(files, _options, _next) {
var options = _options || {}
, outputDir = options.outputDir || process.cwd()
, next = _next || function() {}
, phantomArgs = [
phantomscript
, outputDir
, options.png
, options.svg
, options.verbose
]
files.forEach(function(file) {
phantomArgs.push(file)
})
mkdirp(outputDir, function(err) {
if (err) {
throw err
return
}
phantom = spawn(options.phantomPath, phantomArgs)
phantom.on('exit', next)
phantom.stderr.pipe(process.stderr)
phantom.stdout.pipe(process.stdout)
})
}

213
lib/phantomscript.js Normal file
View File

@ -0,0 +1,213 @@
/**
* Credits:
* - SVG Processing from the NYTimes svg-crowbar, under an MIT license
* https://github.com/NYTimes/svg-crowbar
* - Thanks to the grunticon project for some guidance
* https://github.com/filamentgroup/grunticon
*/
phantom.onError = function(msg, trace) {
var msgStack = ['PHANTOM ERROR: ' + msg]
if (trace && trace.length) {
msgStack.push('TRACE:')
trace.forEach(function(t) {
msgStack.push(
' -> '
+ (t.file || t.sourceURL)
+ ': '
+ t.line
+ (t.function ? ' (in function ' + t.function +')' : '')
)
})
}
system.stderr.write(msgStack.join('\n'))
phantom.exit(1)
}
var system = require('system')
, fs = require('fs')
, webpage = require('webpage')
var page = webpage.create()
, files = phantom.args.slice(4, phantom.args.length)
, options = {
outputDir: phantom.args[0]
, png: phantom.args[1] === 'true' ? true : false
, svg: phantom.args[2] === 'true' ? true : false
, verbose: phantom.args[3] === 'true' ? true : false
}
, log = logger(options.verbose)
page.content = [
'<html>'
, '<head>'
, '<style type="text/css">'
, '* { margin: 0; padding: 0; }'
, '</style>'
, '</head>'
, '<body>'
, '</body>'
, '</html>'
].join('\n')
page.injectJs('../dist/mermaid.full.js')
files.forEach(function(file) {
var contents = fs.read(file)
, filename = file.split(fs.separator).slice(-1)
, oParser = new DOMParser()
, oDOM
, svgContent
, allElements
// this JS is executed in this statement is sandboxed, even though it doesn't
// look like it. we need to serialize then unserialize the svgContent that's
// taken from the DOM
svgContent = page.evaluate(executeInPage, contents)
oDOM = oParser.parseFromString(svgContent, "text/xml")
resolveSVGElement(oDOM.firstChild)
// traverse the SVG, and replace all foreignObject elements
// can be removed when https://github.com/knsv/mermaid/issues/58 is resolved
allElements = traverse(oDOM)
for (var i = 0, len = allElements.length; i < len; i++) {
resolveForeignObjects(allElements[i])
}
if (options.png) {
page.viewportSize = {
width: ~~oDOM.documentElement.attributes.getNamedItem('width').value
, height: ~~oDOM.documentElement.attributes.getNamedItem('height').value
}
page.render(options.outputDir + fs.separator + filename + '.png')
log('saved png: ' + filename + '.png')
}
if (options.svg) {
var serialize = new XMLSerializer()
fs.write(
options.outputDir + fs.separator + filename + '.svg'
, serialize.serializeToString(oDOM)
, 'w'
)
log('saved svg: ' + filename + '.svg')
}
})
phantom.exit()
function logger(_verbose) {
var verbose = _verbose
return function(_message, _level) {
var level = level
, message = _message
, log
log = level === 'error' ? system.stderr : system.stdout
if (verbose) {
log.write(message + '\n')
}
}
}
function traverse(obj){
var tree = []
tree.push(obj)
visit(obj)
function visit(node) {
if (node && node.hasChildNodes()) {
var child = node.firstChild
while (child) {
if (child.nodeType === 1 && child.nodeName != 'SCRIPT'){
tree.push(child)
visit(child)
}
child = child.nextSibling
}
}
}
return tree
}
function resolveSVGElement(element) {
var prefix = {
xmlns: "http://www.w3.org/2000/xmlns/"
, xlink: "http://www.w3.org/1999/xlink"
, svg: "http://www.w3.org/2000/svg"
}
, doctype = '<!DOCTYPE svg:svg PUBLIC'
+ ' "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN"'
+ ' "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">'
element.setAttribute("version", "1.1")
// removing attributes so they aren't doubled up
element.removeAttribute("xmlns")
element.removeAttribute("xlink")
// These are needed for the svg
if (!element.hasAttributeNS(prefix.xmlns, "xmlns")) {
element.setAttributeNS(prefix.xmlns, "xmlns", prefix.svg)
}
if (!element.hasAttributeNS(prefix.xmlns, "xmlns:xlink")) {
element.setAttributeNS(prefix.xmlns, "xmlns:xlink", prefix.xlink)
}
}
function resolveForeignObjects(element) {
var children
, textElement
, textSpan
if (element.tagName === 'foreignObject') {
textElement = document.createElement('text')
textSpan = document.createElement('tspan')
textSpan.setAttribute(
'style'
, 'font-size: 11.5pt; font-family: "sans-serif";'
)
textSpan.setAttribute('x', 0)
textSpan.setAttribute('y', 14.5)
textSpan.textContent = element.textContent
textElement.appendChild(textSpan)
element.parentElement.appendChild(textElement)
element.parentElement.removeChild(element)
}
}
// The sandboxed function that's executed in-page by phantom
function executeInPage(contents) {
var xmlSerializer = new XMLSerializer()
, toRemove
, el
, elContent
, svg
, svgValue
toRemove = document.getElementsByClassName('mermaid')
if (toRemove && toRemove.length) {
for (var i = 0, len = toRemove.length; i < len; i++) {
toRemove[i].parentNode.removeChild(toRemove[i])
}
}
el = document.createElement("div")
el.className = 'mermaid'
elContent = document.createTextNode(contents)
el.appendChild(elContent)
document.body.appendChild(el)
mermaid.init()
svg = document.querySelector('svg')
svgValue = xmlSerializer.serializeToString(svg)
return svgValue
}

View File

@ -1,10 +1,13 @@
{
"name": "mermaid",
"version": "0.2.15",
"version": "0.3.2",
"description": "Markdownish syntax for generating flowcharts",
"main": "src/main.js",
"bin": {
"mermaid": "./bin/mermaid.js"
},
"scripts": {
"test": "gulp coverage"
"test": "gulp test"
},
"repository": {
"type": "git",
@ -13,19 +16,31 @@
"author": "",
"license": "MIT",
"dependencies": {
"chalk": "^0.5.1",
"dagre-d3": "~0.3.2",
"he": "^0.5.0",
"dagre-d3": "~0.3.2"
"minimist": "^1.1.0",
"mkdirp": "^0.5.0",
"semver": "^4.1.1",
"which": "^1.0.8"
},
"devDependencies": {
"async": "^0.9.0",
"browserify": "~6.2.0",
"clone": "^0.2.0",
"codeclimate-test-reporter": "0.0.4",
"d3": "~3.4.13",
"dagre-d3": "~0.3.2",
"event-stream": "^3.2.0",
"foundation": "^4.2.1-1",
"front-matter": "^0.2.0",
"gulp": "~3.8.9",
"gulp-browserify": "^0.5.0",
"gulp-bump": "^0.1.11",
"gulp-concat": "~2.4.1",
"gulp-data": "^1.1.1",
"gulp-ext-replace": "~0.1.0",
"gulp-hogan": "^1.1.0",
"gulp-istanbul": "^0.4.0",
"gulp-jasmine": "~1.0.1",
"gulp-jison": "~1.0.0",
@ -36,6 +51,7 @@
"gulp-tag-version": "^1.2.1",
"gulp-uglify": "~1.0.1",
"he": "^0.5.0",
"hogan.js": "^3.0.2",
"jasmine": "~2.0.1",
"jison": "~0.4.15",
"jshint-stylish": "^1.0.0",
@ -51,9 +67,13 @@
"lodash.defaults": "^2.4.1",
"lodash.templatesettings": "^2.4.1",
"lodash.values": "^2.4.1",
"marked": "^0.3.2",
"mock-browser": "^0.90.27",
"path": "^0.4.9",
"phantomjs": "^1.9.12",
"rewire": "^2.1.3"
"rewire": "^2.1.3",
"rimraf": "^2.2.8",
"semantic-ui": "^1.4.1",
"tape": "^3.0.3"
}
}

View File

@ -38,6 +38,8 @@ exports.addVertices = function (vert, g) {
*/
var classStr = '';
//console.log(vertice.classes);
if(vertice.classes.length >0){
classStr = vertice.classes.join(" ");
}
@ -58,6 +60,7 @@ exports.addVertices = function (vert, g) {
verticeText = vertice.text;
}
<<<<<<< HEAD
var labelTypeStr = '';
if(equals('html',mermaid_config.labelType)) {
labelTypeStr = 'html';
@ -65,6 +68,9 @@ exports.addVertices = function (vert, g) {
verticeText = verticeText.replace(/<br>/g, "\n");
labelTypeStr = 'text';
}
=======
console.log(verticeText);
>>>>>>> master
var radious = 0;
var _shape = '';
@ -115,16 +121,34 @@ exports.addEdges = function (edges, g) {
}
var style = '';
if(typeof edge.style !== 'undefined'){
edge.style.forEach(function(s){
style = style + s +';';
});
}
else{
switch(edge.stroke){
case 'normal':
style = 'stroke: #333; stroke-width: 1.5px;fill:none';
break;
case 'dotted':
style = 'stroke: #333; fill:none;stroke-width:2px;stroke-dasharray:3;';
break;
case 'thick':
style = 'stroke: #333; stroke-width: 3.5px;fill:none';
break;
}
}
// Add the edge to the graph
if (typeof edge.text === 'undefined') {
if(typeof edge.style === 'undefined'){
g.setEdge(edge.start, edge.end,{ style: "stroke: #333; stroke-width: 1.5px;fill:none", arrowheadStyle: "fill: #333", arrowhead: aHead},cnt);
g.setEdge(edge.start, edge.end,{ style: style, arrowhead: aHead},cnt);
}else{
g.setEdge(edge.start, edge.end, {
style: style, arrowheadStyle: "fill: #333", arrowhead: aHead
@ -135,7 +159,11 @@ exports.addEdges = function (edges, g) {
else {
var edgeText = edge.text.replace(/<br>/g, "\n");
if(typeof edge.style === 'undefined'){
<<<<<<< HEAD
g.setEdge(edge.start, edge.end,{labelType: "text", style: "stroke: #333; stroke-width: 1.5px;fill:none", labelpos:'c', label: edgeText, arrowheadStyle: "fill: #333", arrowhead: aHead},cnt);
=======
g.setEdge(edge.start, edge.end,{labelType: "html",style: style, labelpos:'c', label: '<span style="background:#e8e8e8">'+edge.text+'</span>', arrowheadStyle: "fill: #333", arrowhead: aHead},cnt);
>>>>>>> master
}else{
g.setEdge(edge.start, edge.end, {
labelType: "text",style: style, arrowheadStyle: "fill: #333", label: edgeText, arrowhead: aHead
@ -205,7 +233,10 @@ exports.draw = function (text, id,isDot) {
}
// Create the input mermaid.graph
var g = new dagreD3.graphlib.Graph({multigraph:true})
var g = new dagreD3.graphlib.Graph({
multigraph:true,
compound: true
})
.setGraph({
rankdir: dir,
marginx: 20,
@ -216,9 +247,35 @@ exports.draw = function (text, id,isDot) {
return {};
});
var subGraphs = graph.getSubGraphs();
var i = 0;
subGraphs.forEach(function(subG){
i = i + 1;
var id = 'subG'+i;
graph.addVertex(id,undefined,undefined,undefined);
});
// Fetch the verices/nodes and edges/links from the parsed graph definition
var vert = graph.getVertices();
//console.log(vert);
var edges = graph.getEdges();
//g.setParent("A", "p");
//g.setParent("B", "p");
//console.log(subGraphs);
i = 0;
subGraphs.forEach(function(subG){
i = i + 1;
var id = 'subG'+i;
d3.selectAll('cluster').append('text');
subG.nodes.forEach(function(node){
//console.log('Setting node',node,' to subgraph '+id);
g.setParent(node,id);
});
});
exports.addVertices(vert, g);
exports.addEdges(edges, g);
@ -294,8 +351,57 @@ exports.draw = function (text, id,isDot) {
// Run the renderer. This is what draws the final graph.
render(d3.select("#" + id + " g"), g);
var svgb = document.querySelector('#mermaidChart0');
/*
var xPos = document.querySelectorAll('.clusters rect')[0].x.baseVal.value;
var width = document.querySelectorAll('.clusters rect')[0].width.baseVal.value;
var cluster = d3.selectAll('.cluster');
var te = cluster.append('text');
te.attr('x', xPos+width/2);
te.attr('y', 12);
//te.stroke('black');
te.attr('id', 'apa12');
te.style('text-anchor', 'middle');
te.text('Title for cluster');
*/
// Center the graph
svg.attr("height", g.graph().height );
svg.attr("width", g.graph().width );
svg.attr("viewBox", svgb.getBBox().x + ' 0 '+ g.graph().width+' '+ g.graph().height);
setTimeout(function(){
console.log('Fixing titles');
var i = 0;
subGraphs.forEach(function(subG){
console.log('Setting id '+id);
var clusterRects = document.querySelectorAll('#' + id + ' .clusters rect');
var clusters = document.querySelectorAll('#' + id + ' .cluster');
if(subG.title !== 'undefined'){
console.log(clusterRects[i]);
var xPos = clusterRects[i].x.baseVal.value;
var yPos = clusterRects[i].y.baseVal.value;
var width = clusterRects[i].width.baseVal.value;
var cluster = d3.select(clusters[i]);
var te = cluster.append('text');
te.attr('x', xPos+width/2);
te.attr('y', yPos +14);
te.attr('fill', 'black');
te.attr('stroke','none');
te.attr('id', id+'Text');
te.style('text-anchor', 'middle');
console.log('Title '+subG.title);
console.log('i',i);
console.log('x'+xPos+width/2);
console.log('y'+xPos);
te.text(subG.title);
}
i = i + 1;
});
},200);
};

View File

@ -5,6 +5,7 @@
var vertices = {};
var edges = [];
var classes = [];
var subGraphs = [];
var direction;
// Functions to be run after graph rendering
var funs = [];
@ -63,6 +64,7 @@ exports.addLink = function (start, end, type, linktext) {
if (typeof type !== 'undefined') {
edge.type = type.type;
edge.stroke = type.stroke;
}
edges.push(edge);
};
@ -197,6 +199,7 @@ exports.clear = function () {
classes = {};
edges = [];
funs = [];
subGraphs = [];
};
/**
*
@ -205,3 +208,30 @@ exports.clear = function () {
exports.defaultStyle = function () {
return "fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;";
};
/**
* Clears the internal graph db so that a new graph can be parsed.
*/
exports.addSubGraph = function (list, title) {
function uniq(a) {
var prims = {"boolean":{}, "number":{}, "string":{}}, objs = [];
return a.filter(function(item) {
var type = typeof item;
if(type in prims)
return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true);
else
return objs.indexOf(item) >= 0 ? false : objs.push(item);
});
}
var subG = [];
subG = uniq(subG.concat.apply(subG,list));
//console.log(subG);
subGraphs.push({nodes:subG,title:title});
};
exports.getSubGraphs = function (list) {
return subGraphs;
};

View File

@ -10,31 +10,50 @@
"class" return 'CLASS';
"click" return 'CLICK';
"graph" return 'GRAPH';
"subgraph" return 'subgraph';
"end" return 'end';
"LR" return 'DIR';
"RL" return 'DIR';
"TB" return 'DIR';
"BT" return 'DIR';
"TD" return 'DIR';
"BR" return 'DIR';
[0-9] return 'NUM';
[0-9]+ return 'NUM';
\# return 'BRKT';
":" return 'COLON';
";" return 'SEMI';
"," return 'COMMA';
"=" return 'EQUALS';
"*" return 'MULT';
"." return 'DOT';
"<" return 'TAGSTART';
">" return 'TAGEND';
"^" return 'UP';
"v" return 'DOWN';
\-\-[x] return 'ARROW_CROSS';
\-\-\> return 'ARROW_POINT';
\-\-[o] return 'ARROW_CIRCLE';
\-\-\- return 'ARROW_OPEN';
\-\.\-[x] return 'DOTTED_ARROW_CROSS';
\-\.\-\> return 'DOTTED_ARROW_POINT';
\-\.\-[o] return 'DOTTED_ARROW_CIRCLE';
\-\.\- return 'DOTTED_ARROW_OPEN';
.\-[x] return 'DOTTED_ARROW_CROSS';
\.\-\> return 'DOTTED_ARROW_POINT';
\.\-[o] return 'DOTTED_ARROW_CIRCLE';
\.\- return 'DOTTED_ARROW_OPEN';
\=\=[x] return 'THICK_ARROW_CROSS';
\=\=\> return 'THICK_ARROW_POINT';
\=\=[o] return 'THICK_ARROW_CIRCLE';
\=\=[\=] return 'THICK_ARROW_OPEN';
\-\- return '--';
\-\. return '-.';
\=\= return '==';
\- return 'MINUS';
"." return 'DOT';
\+ return 'PLUS';
\% return 'PCT';
"=" return 'EQUALS';
\= return 'EQUALS';
[\u0021-\u0027\u002A-\u002E\u003F\u0041-\u005A\u0061-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u0021-\u0027\u002A-\u002E\u003F\u0041-\u005A\u005C\u005F-\u007A\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
@ -114,25 +133,47 @@
%left '^'
%start expressions
%start mermaidDoc
%% /* language grammar */
expressions
: graphConfig statements EOF
| graphConfig spaceListNewline statements EOF
{$$=$1;}
;
mermaidDoc: graphConfig document ;
document
: /* empty */
{ $$ = [];}
| document line
{
if($2 !== []){
$1.push($2);
}
$$=$1;}
;
line
: spaceListNewline statement
{$$=$2;}
| statement
{$$=$1;}
| SEMI
| EOF
;
graphConfig
: GRAPH SPACE DIR SEMI
: GRAPH SPACE DIR FirstStmtSeperator
{ yy.setDirection($3);$$ = $3;}
| GRAPH SPACE TAGEND FirstStmtSeperator
{ yy.setDirection("LR");$$ = $3;}
| GRAPH SPACE TAGSTART FirstStmtSeperator
{ yy.setDirection("RL");$$ = $3;}
| GRAPH SPACE UP FirstStmtSeperator
{ yy.setDirection("BT");$$ = $3;}
| GRAPH SPACE DOWN FirstStmtSeperator
{ yy.setDirection("TB");$$ = $3;}
;
statements
: statement spaceListNewline statements
| statement
;
FirstStmtSeperator
: SEMI | NEWLINE | spaceList NEWLINE ;
spaceListNewline
@ -150,20 +191,36 @@ spaceList
statement
: commentStatement NEWLINE
{$$='Comment';}
| verticeStatement SEMI
| styleStatement SEMI
| linkStyleStatement SEMI
| classDefStatement SEMI
| classStatement SEMI
| clickStatement SEMI
{$$=[];}
| verticeStatement separator
{$$=$1}
| styleStatement separator
{$$=[];}
| linkStyleStatement separator
{$$=[];}
| classDefStatement separator
{$$=[];}
| classStatement separator
{$$=[];}
| clickStatement separator
{$$=[];}
| subgraph text separator document endStatement separator
{yy.addSubGraph($4,$2);}
| subgraph separator document endStatement separator
{yy.addSubGraph($3,undefined);}
;
endStatement: end
| SPACE endStatement
;
separator: NEWLINE | SEMI | EOF ;
verticeStatement:
vertex link vertex
{ yy.addLink($1,$3,$2);$$ = 'oy'}
{ yy.addLink($1,$3,$2);$$ = [$1,$3];}
| vertex
{$$ = 'yo';}
{$$ = [$1];}
;
vertex: alphaNum SQS text SQE
@ -186,8 +243,6 @@ vertex: alphaNum SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'odd');}
| alphaNum TAGEND text SQE SPACE
{$$ = $1;yy.addVertex($1,$3,'odd');}
| alphaNum TAGSTART text TAGEND
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| alphaNum
{$$ = $1;yy.addVertex($1);}
| alphaNum SPACE
@ -197,7 +252,7 @@ vertex: alphaNum SQS text SQE
alphaNum
: alphaNumStatement
{$$=$1;}
| alphaNumStatement alphaNum
| alphaNum alphaNumStatement
{$$=$1+''+$2;}
;
@ -217,17 +272,45 @@ link: linkStatement arrowText
{$$ = $1;}
| linkStatement SPACE
{$$ = $1;}
| '--' SPACE text SPACE linkStatement
{$5.text = $3;$$ = $5;}
| '--' SPACE text SPACE linkStatement SPACE
{$5.text = $3;$$ = $5;}
| '-.' SPACE text SPACE linkStatement
{$5.text = $3;$$ = $5;}
| '-.' SPACE text SPACE linkStatement SPACE
{$5.text = $3;$$ = $5;}
| '==' SPACE text SPACE linkStatement
{$5.text = $3;$$ = $5;}
| '==' SPACE text SPACE linkStatement SPACE
{$5.text = $3;$$ = $5;}
;
linkStatement: ARROW_POINT
{$$ = {"type":"arrow"};}
{$$ = {"type":"arrow","stroke":"normal"};}
| ARROW_CIRCLE
{$$ = {"type":"arrow_circle"};}
{$$ = {"type":"arrow_circle","stroke":"normal"};}
| ARROW_CROSS
{$$ = {"type":"arrow_cross"};}
{$$ = {"type":"arrow_cross","stroke":"normal"};}
| ARROW_OPEN
{$$ = {"type":"arrow_open"};}
;
{$$ = {"type":"arrow_open","stroke":"normal"};}
| DOTTED_ARROW_POINT
{$$ = {"type":"arrow","stroke":"dotted"};}
| DOTTED_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"dotted"};}
| DOTTED_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"dotted"};}
| DOTTED_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"dotted"};}
| THICK_ARROW_POINT
{$$ = {"type":"arrow","stroke":"thick"};}
| THICK_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"thick"};}
| THICK_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"thick"};}
| THICK_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"thick"};}
;
arrowText:
PIPE text PIPE
@ -250,7 +333,7 @@ commentText: commentToken
keywords
: STYLE | LINKSTYLE | CLASSDEF | CLASS | CLICK | GRAPH | DIR;
: STYLE | LINKSTYLE | CLASSDEF | CLASS | CLICK | GRAPH | DIR | subgraph | end ;
textNoTags: textNoTagsToken
@ -296,13 +379,13 @@ style: styleComponent
{$$ = $1 + $2;}
;
styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT;
styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT | STYLE | PCT ;
/* Token lists */
commentToken : textToken | graphCodeTokens ;
textToken : textNoTagsToken | TAGSTART | TAGEND ;
textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' ;
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;

File diff suppressed because one or more lines are too long

View File

@ -10,12 +10,12 @@ describe('when parsing ',function(){
flow.parser.yy = require('../graphDb');
flow.parser.yy.clear();
/*flow.parser.parse.parseError= function parseError(str, hash) {
console.log(str);
console.logconsole.log(str);
}*/
});
it('should handle a nodes and edges',function(){
var res = flow.parser.parse('graph TD;A-->B;');
var res = flow.parser.parse('graph TD;\nA-->B;');
var vert = flow.parser.yy.getVertices();
@ -30,6 +30,84 @@ describe('when parsing ',function(){
expect(edges[0].text).toBe('');
});
it('should handle angle bracket '>' as direction LR',function(){
var res = flow.parser.parse('graph >;A-->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
var direction = flow.parser.yy.getDirection();
expect(direction).toBe('LR');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle angle bracket '<' as direction RL',function(){
var res = flow.parser.parse('graph <;A-->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
var direction = flow.parser.yy.getDirection();
expect(direction).toBe('RL');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle caret '^' as direction BT',function(){
var res = flow.parser.parse('graph ^;A-->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
var direction = flow.parser.yy.getDirection();
expect(direction).toBe('BT');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle lower-case \'v\' as direction TB',function(){
var res = flow.parser.parse('graph v;A-->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
var direction = flow.parser.yy.getDirection();
expect(direction).toBe('TB');
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle a nodes and edges and a space between link and node',function(){
var res = flow.parser.parse('graph TD;A --> B;');
@ -46,6 +124,37 @@ describe('when parsing ',function(){
expect(edges[0].text).toBe('');
});
it('should handle a nodes and edges, a space between link and node and each line ending without semicolon',function(){
var res = flow.parser.parse('graph TD\nA --> B\n style e red');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle statements ending without semicolon',function(){
var res = flow.parser.parse('graph TD\nA-->B\nB-->C');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['A'].id).toBe('A');
expect(vert['B'].id).toBe('B');
expect(edges.length).toBe(2);
expect(edges[1].start).toBe('B');
expect(edges[1].end).toBe('C');
expect(edges[0].type).toBe('arrow');
expect(edges[0].text).toBe('');
});
it('should handle a comments',function(){
var res = flow.parser.parse('graph TD;\n%% CComment\n A-->B;');
@ -95,7 +204,7 @@ describe('when parsing ',function(){
});
it('it should handle a trailing whitespaces after statememnts',function(){
var res = flow.parser.parse('graph TD;\n\n\n %% CComment\n A-->B; \nB-->C;');
var res = flow.parser.parse('graph TD;\n\n\n %% CComment\n A-->B; \n B-->C;');
var vert = flow.parser.yy.getVertices();
@ -139,76 +248,341 @@ describe('when parsing ',function(){
expect(edges[0].type).toBe('arrow_circle');
});
it('should handle text on edges without space',function(){
var res = flow.parser.parse('graph TD;A--x|textNoSpace|B;');
it('should handle subgraphs',function(){
var res = flow.parser.parse('graph TD;A-->B;subgraph myTitle;c-->d;end;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].type).toBe('arrow');
});
it('should handle text on edges without space and space between vertices and link',function(){
var res = flow.parser.parse('graph TD;A --x|textNoSpace| B;');
it('should handle subgraphs',function(){
var res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend\n');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].type).toBe('arrow');
});
it('should handle text on edges with space',function(){
var res = flow.parser.parse('graph TD;A--x|text including space|B;');
it('should handle subgraphs',function(){
var res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].type).toBe('arrow');
});
it('should handle text on edges with space',function(){
var res = flow.parser.parse('graph TD;A--x|text with / should work|B;');
it('should handle subgraphs',function(){
var res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-- text -->d\nd-->e\n end;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text with / should work');
expect(edges[0].type).toBe('arrow');
});
it('should handle text on edges with space CAPS',function(){
var res = flow.parser.parse('graph TD;A--x|text including CAPS space|B;');
it('should handle classDefs with style in classes',function(){
var res = flow.parser.parse('graph TD\nA-->B\nclassDef exClass font-style:bold;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].type).toBe('arrow');
});
it('should handle text on edges with space dir',function(){
var res = flow.parser.parse('graph TD;A--x|text including URL space|B;');
it('should handle classDefs with % in classes',function(){
var res = flow.parser.parse('graph TD\nA-->B\nclassDef exClass fill:#f96,stroke:#333,stroke-width:4px,font-size:50%,font-style:bold;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including URL space');
expect(edges[0].type).toBe('arrow');
});
it('should handle text on edges with graph keyword',function(){
var res = flow.parser.parse('graph TD;A--x|text including graph space|B;');
it('should handle style definitons with more then 1 digit in a row',function(){
var res = flow.parser.parse('graph TD\n' +
'A-->B1\n' +
'A-->B2\n' +
'A-->B3\n' +
'A-->B4\n' +
'A-->B5\n' +
'A-->B6\n' +
'A-->B7\n' +
'A-->B8\n' +
'A-->B9\n' +
'A-->B10\n' +
'A-->B11\n' +
'linkStyle 10 stroke-width:1px;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text including graph space');
expect(edges[0].type).toBe('arrow');
});
describe("it should handle text on edges",function(){
it('it should handle text without space',function(){
var res = flow.parser.parse('graph TD;A--x|textNoSpace|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle with space',function(){
var res = flow.parser.parse('graph TD;A--x|text including space|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('it should handle text with /',function(){
var res = flow.parser.parse('graph TD;A--x|text with / should work|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text with / should work');
});
it('it should handle space and space between vertices and link',function(){
var res = flow.parser.parse('graph TD;A --x|textNoSpace| B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and CAPS',function(){
var res = flow.parser.parse('graph TD;A--x|text including CAPS space|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and dir',function(){
var res = flow.parser.parse('graph TD;A--x|text including URL space|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including URL space');
});
it('should handle space and dir (TD)',function(){
var res = flow.parser.parse('graph TD;A--x|text including R TD space|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including R TD space');
});
it('should handle `',function(){
var res = flow.parser.parse('graph TD;A--x|text including `|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including `');
});
it('should handle keywords',function(){
var res = flow.parser.parse('graph TD;A--x|text including graph space|B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text including graph space');
});
});
describe("it should handle new line type notation",function() {
it('it should handle regular lines', function () {
var res = flow.parser.parse('graph TD;A-->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('normal');
});
it('it should handle dotted lines', function () {
var res = flow.parser.parse('graph TD;A-.->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('dotted');
});
it('it should handle dotted lines', function () {
var res = flow.parser.parse('graph TD;A==>B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('thick');
});
it('it should handle text on lines', function () {
var res = flow.parser.parse('graph TD;A-- test text with == -->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('normal');
});
it('it should handle text on lines', function () {
var res = flow.parser.parse('graph TD;A-. test text with == .->B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('dotted');
});
it('it should handle text on lines', function () {
var res = flow.parser.parse('graph TD;A== test text with -- ==>B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].stroke).toBe('thick');
});
});
describe("it should handle text on edges using the new notation",function(){
it('it should handle text without space',function(){
var res = flow.parser.parse('graph TD;A-- textNoSpace --xB;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('it should handle text with multiple leading space',function(){
var res = flow.parser.parse('graph TD;A-- textNoSpace --xB;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle with space',function(){
var res = flow.parser.parse('graph TD;A-- text including space --xB;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('it should handle text with /',function(){
var res = flow.parser.parse('graph TD;A -- text with / should work --x B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text with / should work');
});
it('it should handle space and space between vertices and link',function(){
var res = flow.parser.parse('graph TD;A -- textNoSpace --x B;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and CAPS',function(){
var res = flow.parser.parse('graph TD;A-- text including CAPS space --xB;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
});
it('should handle space and dir',function(){
var res = flow.parser.parse('graph TD;A-- text including URL space --xB;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including URL space');
});
it('should handle space and dir (TD)',function(){
var res = flow.parser.parse('graph TD;A-- text including R TD space --xB;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].type).toBe('arrow_cross');
expect(edges[0].text).toBe('text including R TD space');
});
it('should handle keywords',function(){
var res = flow.parser.parse('graph TD;A-- text including graph space --xB;');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(edges[0].text).toBe('text including graph space');
});
});
it('should handle multi-line text',function(){
var res = flow.parser.parse('graph TD;A--o|text space|B;\n B-->|more text with space|C;');
@ -229,6 +603,7 @@ describe('when parsing ',function(){
expect(edges[1].text).toBe('more text with space');
});
it('should handle multiple edges',function(){
var res = flow.parser.parse('graph TD;A---|This is the 123 s text|B;\nA---|This is the second edge|B;');
var vert = flow.parser.yy.getVertices();
@ -314,60 +689,79 @@ describe('when parsing ',function(){
expect(edges[0].text).toBe(',.?!+-*');
});
describe("it should handle text in vertices, ",function(){
it('should handle text in vertices with space',function(){
var res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar);');
it('it should handle space',function(){
var res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('Chimpansen hoppar');
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('Chimpansen hoppar');
});
it('it should handle åäö and minus',function(){
var res = flow.parser.parse('graph TD;A-->C{Chimpansen hoppar åäö-ÅÄÖ};');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('diamond');
expect(vert['C'].text).toBe('Chimpansen hoppar åäö-ÅÄÖ');
});
it('it should handle with åäö, minus and space and br',function(){
var res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar åäö <br> - ÅÄÖ);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('Chimpansen hoppar åäö <br> - ÅÄÖ');
});
xit('it should handle åäö, minus and space and br',function(){
var res = flow.parser.parse('graph TD; A[Object&#40;foo,bar&#41;]-->B(Thing);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe(' A[Object&#40;foo,bar&#41;]-->B(Thing);');
});
it('it should handle unicode chars',function(){
var res = flow.parser.parse('graph TD;A-->C(Начало);');
var vert = flow.parser.yy.getVertices();
expect(vert['C'].text).toBe('Начало');
});
it('it should handle backslask',function(){
var res = flow.parser.parse('graph TD;A-->C(c:\\windows);');
var vert = flow.parser.yy.getVertices();
expect(vert['C'].text).toBe('c:\\windows');
});
it('it should handle CAPS',function(){
var res = flow.parser.parse('graph TD;A-->C(some CAPS);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('some CAPS');
});
it('it should handle directions',function(){
var res = flow.parser.parse('graph TD;A-->C(some URL);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('some URL');
});
});
it('should handle text in vertices with åäö and minus',function(){
var res = flow.parser.parse('graph TD;A-->C{Chimpansen hoppar åäö-ÅÄÖ};');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('diamond');
expect(vert['C'].text).toBe('Chimpansen hoppar åäö-ÅÄÖ');
});
it('should handle text in vertices with åäö, minus and space and br',function(){
var res = flow.parser.parse('graph TD;A-->C(Chimpansen hoppar åäö <br> - ÅÄÖ);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('Chimpansen hoppar åäö <br> - ÅÄÖ');
});
it('should handle text in vertices with unicode chars',function(){
var res = flow.parser.parse('graph TD;A-->C(Начало);');
var vert = flow.parser.yy.getVertices();
expect(vert['C'].text).toBe('Начало');
});
it('should handle text in vertices with CAPS',function(){
var res = flow.parser.parse('graph TD;A-->C(some CAPS);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('some CAPS');
});
it('should handle text in vertices with directions',function(){
var res = flow.parser.parse('graph TD;A-->C(some URL);');
var vert = flow.parser.yy.getVertices();
var edges = flow.parser.yy.getEdges();
expect(vert['C'].type).toBe('round');
expect(vert['C'].text).toBe('some URL');
});
it('should handle a single node',function(){
// Silly but syntactically correct
var res = flow.parser.parse('graph TD;A;');

View File

@ -14,9 +14,19 @@
%%
[\n]+ return 'NL';
[\-][x] { return 'SOLID_CROSS';}
[\-][\-][x] { return 'DOTTED_CROSS';}
[\-][>][>] { return 'SOLID_ARROW';}
[\-][\-][>][>] { return 'DOTTED_ARROW';}
\s+ /* skip whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"participant" return 'participant';
"opt" return 'opt';
"loop" return 'loop';
"alt" return 'alt';
"else" return 'else';
"end" return 'end';
"left of" return 'left_of';
"right of" return 'right_of';
"over" return 'over';
@ -24,47 +34,79 @@
"title" return 'title';
"sequenceDiagram" return 'SD';
"," return ',';
[^\->:\n,]+ return 'ACTOR';
"--" return 'DOTLINE';
"-" return 'LINE';
">>" return 'OPENARROW';
">" return 'ARROW';
:[^#\n]+ return 'MESSAGE';
";" return 'NL';
[^\->:\n,;]+ return 'ACTOR';
"->" return 'SOLID_OPEN_ARROW';
"-->" return 'DOTTED_OPEN_ARROW';
"->>" return 'SOLID_ARROW';
"-->>" return 'DOTTED_ARROW';
":"[^#\n;]+ return 'TXT';
<<EOF>> return 'EOF';
. return 'INVALID';
/lex
%left '^'
%start start
%% /* language grammar */
start
: SD document 'EOF' { return yy; }
: SD document 'EOF' { yy.apply($2);return $2; }
;
document
: /* empty */
| document line
: /* empty */ { $$ = [] }
| document line {$1.push($2);$$ = $1}
;
line
: statement { }
| 'NL'
: SPACE statement { $$ = $2 }
| statement { $$ = $1 }
| NL { $$=[];}
| EOF { $$=[];}
;
statement
: 'participant' actor { $$='actor'; }
| signal { $$='signal'; }
| note_statement { $$='note'; }
| 'title' message { yy.setTitle($2); }
: 'participant' actor 'NL' {$$=$2;}
| signal 'NL'
| note_statement 'NL'
| 'title' SPACE text 'NL'
| 'loop' actor document end
{
$3.unshift({type: 'loopStart', loopText:$2.actor, signalType: yy.LINETYPE.LOOP_START});
$3.push({type: 'loopEnd', loopText:$2, signalType: yy.LINETYPE.LOOP_END});
$$=$3;}
| opt actor document end
{
$3.unshift({type: 'optStart', optText:$2.actor, signalType: yy.LINETYPE.OPT_START});
$3.push({type: 'optEnd', optText:$2.actor, signalType: yy.LINETYPE.OPT_END});
$$=$3;}
| alt actor document else actor document end
{
// Alt start
$3.unshift({type: 'altStart', altText:$2.actor, signalType: yy.LINETYPE.ALT_START});
// Content in alt is already in $3
// Else
$3.push({type: 'else', altText:$5.actor, signalType: yy.LINETYPE.ALT_ELSE});
// Content in other alt
$3 = $3.concat($6);
// End
$3.push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END});
$$=$3;}
;
note_statement
: 'note' placement actor message { $$ = yy.addNote($3, $2, $4); }
| 'note' 'over' actor_pair message { $$ = yy.addNote($3, yy.PLACEMENT.OVER, $4); }
: 'note' placement actor text2 {$$=[$3,{type:'addNote', placement:$2, actor:$3.actor, text:$4}];}
| 'note' 'over' spaceList actor_pair actor
;
spaceList
: SPACE spaceList
| SPACE
;
actor_pair
: actor { $$ = $1; }
| actor ',' actor { $$ = [$1, $3]; }
@ -76,32 +118,26 @@ placement
;
signal
: actor signaltype actor message
{ yy.addSignal($1, $3, $4, $2); }
: actor signaltype actor text2
{$$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
;
actors: actors actor
| actor
;
actor
/*: ACTOR { $$ = yy.getActor($1); }*/
: ACTOR { yy.addActor($1,$1,$1); }
: ACTOR {$$={type: 'addActor', actor:$1}}
;
signaltype
: linetype arrowtype { $$ = $1 | ($2 << 2); }
| linetype { $$ = $1; }
: SOLID_OPEN_ARROW { $$ = yy.LINETYPE.SOLID_OPEN; }
| DOTTED_OPEN_ARROW { $$ = yy.LINETYPE.DOTTED_OPEN; }
| SOLID_ARROW { $$ = yy.LINETYPE.SOLID; }
| DOTTED_ARROW { $$ = yy.LINETYPE.DOTTED; }
| SOLID_CROSS { $$ = yy.LINETYPE.SOLID_CROSS; }
| DOTTED_CROSS { $$ = yy.LINETYPE.DOTTED_CROSS; }
;
linetype
: LINE { $$ = yy.LINETYPE.SOLID; }
| DOTLINE { $$ = yy.LINETYPE.DOTTED; }
;
arrowtype
: ARROW { $$ = yy.ARROWTYPE.FILLED; }
| OPENARROW { $$ = yy.ARROWTYPE.OPEN; }
;
message
: MESSAGE { $$ = $1.substring(1).trim().replace(/\\n/gm, "\n"); }
;
text2: TXT {$$ = $1.substring(1).trim().replace(/\\n/gm, "\n");} ;
%%

View File

@ -72,81 +72,107 @@
}
*/
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,14,16,24],$V1=[1,14],$V2=[1,17],$V3=[24,29,30];
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,15,17,19,20,22,33],$V1=[2,2],$V2=[1,6],$V3=[1,8],$V4=[1,9],$V5=[1,12],$V6=[1,13],$V7=[1,14],$V8=[1,15],$V9=[1,17],$Va=[1,18],$Vb=[2,7],$Vc=[6,8,10,11,15,17,18,19,20,21,22,33],$Vd=[6,8,10,11,15,17,18,19,20,22,33],$Ve=[1,46],$Vf=[1,49],$Vg=[1,53];
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"start":3,"SD":4,"document":5,"EOF":6,"line":7,"statement":8,"NL":9,"participant":10,"actor":11,"signal":12,"note_statement":13,"title":14,"message":15,"note":16,"placement":17,"over":18,"actor_pair":19,",":20,"left_of":21,"right_of":22,"signaltype":23,"ACTOR":24,"linetype":25,"arrowtype":26,"LINE":27,"DOTLINE":28,"ARROW":29,"OPENARROW":30,"MESSAGE":31,"$accept":0,"$end":1},
terminals_: {2:"error",4:"SD",6:"EOF",9:"NL",10:"participant",14:"title",16:"note",18:"over",20:",",21:"left_of",22:"right_of",24:"ACTOR",27:"LINE",28:"DOTLINE",29:"ARROW",30:"OPENARROW",31:"MESSAGE"},
productions_: [0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,2],[8,1],[8,1],[8,2],[13,4],[13,4],[19,1],[19,3],[17,1],[17,1],[12,4],[11,1],[23,2],[23,1],[25,1],[25,1],[26,1],[26,1],[15,1]],
symbols_: {"error":2,"start":3,"SD":4,"document":5,"EOF":6,"line":7,"SPACE":8,"statement":9,"NL":10,"participant":11,"actor":12,"signal":13,"note_statement":14,"title":15,"text":16,"loop":17,"end":18,"opt":19,"alt":20,"else":21,"note":22,"placement":23,"text2":24,"over":25,"spaceList":26,"actor_pair":27,",":28,"left_of":29,"right_of":30,"signaltype":31,"actors":32,"ACTOR":33,"SOLID_OPEN_ARROW":34,"DOTTED_OPEN_ARROW":35,"SOLID_ARROW":36,"DOTTED_ARROW":37,"SOLID_CROSS":38,"DOTTED_CROSS":39,"TXT":40,"$accept":0,"$end":1},
terminals_: {2:"error",4:"SD",6:"EOF",8:"SPACE",10:"NL",11:"participant",15:"title",16:"text",17:"loop",18:"end",19:"opt",20:"alt",21:"else",22:"note",25:"over",28:",",29:"left_of",30:"right_of",33:"ACTOR",34:"SOLID_OPEN_ARROW",35:"DOTTED_OPEN_ARROW",36:"SOLID_ARROW",37:"DOTTED_ARROW",38:"SOLID_CROSS",39:"DOTTED_CROSS",40:"TXT"},
productions_: [0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,3],[9,2],[9,2],[9,4],[9,4],[9,4],[9,7],[14,4],[14,5],[26,2],[26,1],[27,1],[27,3],[23,1],[23,1],[13,4],[32,2],[32,1],[12,1],[31,1],[31,1],[31,1],[31,1],[31,1],[31,1],[24,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;
yy.apply($$[$0-1]);return $$[$0-1];
break;
case 4:
case 2:
this.$ = []
break;
case 6:
this.$='actor';
case 3:
$$[$0-1].push($$[$0]);this.$ = $$[$0-1]
break;
case 7:
this.$='signal';
case 4: case 5:
this.$ = $$[$0]
break;
case 6: case 7:
this.$=[];
break;
case 8:
this.$='note';
this.$=$$[$0-1];
break;
case 9:
yy.setTitle($$[$0]);
break;
case 10:
this.$ = yy.addNote($$[$0-1], $$[$0-2], $$[$0]);
break;
case 11:
this.$ = yy.addNote($$[$0-1], yy.PLACEMENT.OVER, $$[$0]);
break;
case 12: case 19:
this.$ = $$[$0];
case 12:
$$[$0-1].unshift({type: 'loopStart', loopText:$$[$0-2].actor, signalType: yy.LINETYPE.LOOP_START});
$$[$0-1].push({type: 'loopEnd', loopText:$$[$0-2], signalType: yy.LINETYPE.LOOP_END});
this.$=$$[$0-1];
break;
case 13:
this.$ = [$$[$0-2], $$[$0]];
$$[$0-1].unshift({type: 'optStart', optText:$$[$0-2].actor, signalType: yy.LINETYPE.OPT_START});
$$[$0-1].push({type: 'optEnd', optText:$$[$0-2].actor, signalType: yy.LINETYPE.OPT_END});
this.$=$$[$0-1];
break;
case 14:
this.$ = yy.PLACEMENT.LEFTOF;
// Alt start
$$[$0-4].unshift({type: 'altStart', altText:$$[$0-5].actor, signalType: yy.LINETYPE.ALT_START});
// Content in alt is already in $$[$0-4]
// Else
$$[$0-4].push({type: 'else', altText:$$[$0-2].actor, signalType: yy.LINETYPE.ALT_ELSE});
// Content in other alt
$$[$0-4] = $$[$0-4].concat($$[$0-1]);
// End
$$[$0-4].push({type: 'altEnd', signalType: yy.LINETYPE.ALT_END});
this.$=$$[$0-4];
break;
case 15:
this.$ = yy.PLACEMENT.RIGHTOF;
this.$=[$$[$0-1],{type:'addNote', placement:$$[$0-2], actor:$$[$0-1].actor, text:$$[$0]}];
break;
case 16:
yy.addSignal($$[$0-3], $$[$0-1], $$[$0], $$[$0-2]);
break;
case 17:
yy.addActor($$[$0],$$[$0],$$[$0]);
break;
case 18:
this.$ = $$[$0-1] | ($$[$0] << 2);
case 19:
this.$ = $$[$0];
break;
case 20:
this.$ = yy.LINETYPE.SOLID;
this.$ = [$$[$0-2], $$[$0]];
break;
case 21:
this.$ = yy.LINETYPE.DOTTED;
this.$ = yy.PLACEMENT.LEFTOF;
break;
case 22:
this.$ = yy.ARROWTYPE.FILLED;
this.$ = yy.PLACEMENT.RIGHTOF;
break;
case 23:
this.$ = yy.ARROWTYPE.OPEN;
this.$ = [$$[$0-3],$$[$0-1],{type: 'addMessage', from:$$[$0-3].actor, to:$$[$0-1].actor, signalType:$$[$0-2], msg:$$[$0]}]
break;
case 24:
this.$ = $$[$0].substring(1).trim().replace(/\\n/gm, "\n");
case 26:
this.$={type: 'addActor', actor:$$[$0]}
break;
case 27:
this.$ = yy.LINETYPE.SOLID_OPEN;
break;
case 28:
this.$ = yy.LINETYPE.DOTTED_OPEN;
break;
case 29:
this.$ = yy.LINETYPE.SOLID;
break;
case 30:
this.$ = yy.LINETYPE.DOTTED;
break;
case 31:
this.$ = yy.LINETYPE.SOLID_CROSS;
break;
case 32:
this.$ = yy.LINETYPE.DOTTED_CROSS;
break;
case 33:
this.$ = $$[$0].substring(1).trim().replace(/\\n/gm, "\n");
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],11:12,12:9,13:10,14:[1,11],16:[1,13],24:$V1},{1:[2,1]},o($V0,[2,3]),o($V0,[2,4]),o($V0,[2,5]),{11:15,24:$V1},o($V0,[2,7]),o($V0,[2,8]),{15:16,31:$V2},{23:18,25:19,27:[1,20],28:[1,21]},{17:22,18:[1,23],21:[1,24],22:[1,25]},o([6,9,10,14,16,20,24,27,28,31],[2,17]),o($V0,[2,6]),o($V0,[2,9]),o($V0,[2,24]),{11:26,24:$V1},{24:[2,19],26:27,29:[1,28],30:[1,29]},o($V3,[2,20]),o($V3,[2,21]),{11:30,24:$V1},{11:32,19:31,24:$V1},{24:[2,14]},{24:[2,15]},{15:33,31:$V2},{24:[2,18]},{24:[2,22]},{24:[2,23]},{15:34,31:$V2},{15:35,31:$V2},{20:[1,36],31:[2,12]},o($V0,[2,16]),o($V0,[2,10]),o($V0,[2,11]),{11:37,24:$V1},{31:[2,13]}],
defaultActions: {4:[2,1],24:[2,14],25:[2,15],27:[2,18],28:[2,22],29:[2,23],37:[2,13]},
table: [{3:1,4:[1,2]},{1:[3]},o($V0,$V1,{5:3}),{6:[1,4],7:5,8:$V2,9:7,10:$V3,11:$V4,12:16,13:10,14:11,15:$V5,17:$V6,19:$V7,20:$V8,22:$V9,33:$Va},o($V0,$Vb,{1:[2,1]}),o($Vc,[2,3]),{9:19,11:$V4,12:16,13:10,14:11,15:$V5,17:$V6,19:$V7,20:$V8,22:$V9,33:$Va},o($Vc,[2,5]),o($Vc,[2,6]),{12:20,33:$Va},{10:[1,21]},{10:[1,22]},{8:[1,23]},{12:24,33:$Va},{12:25,33:$Va},{12:26,33:$Va},{31:27,34:[1,28],35:[1,29],36:[1,30],37:[1,31],38:[1,32],39:[1,33]},{23:34,25:[1,35],29:[1,36],30:[1,37]},o([6,8,10,11,15,17,18,19,20,21,22,28,33,34,35,36,37,38,39,40],[2,26]),o($Vc,[2,4]),{10:[1,38]},o($Vc,[2,9]),o($Vc,[2,10]),{16:[1,39]},o($Vd,$V1,{5:40}),o($Vd,$V1,{5:41}),o([6,8,10,11,15,17,19,20,21,22,33],$V1,{5:42}),{12:43,33:$Va},{33:[2,27]},{33:[2,28]},{33:[2,29]},{33:[2,30]},{33:[2,31]},{33:[2,32]},{12:44,33:$Va},{8:$Ve,26:45},{33:[2,21]},{33:[2,22]},o($Vc,[2,8]),{10:[1,47]},{6:$Vf,7:5,8:$V2,9:7,10:$V3,11:$V4,12:16,13:10,14:11,15:$V5,17:$V6,18:[1,48],19:$V7,20:$V8,22:$V9,33:$Va},{6:$Vf,7:5,8:$V2,9:7,10:$V3,11:$V4,12:16,13:10,14:11,15:$V5,17:$V6,18:[1,50],19:$V7,20:$V8,22:$V9,33:$Va},{6:$Vf,7:5,8:$V2,9:7,10:$V3,11:$V4,12:16,13:10,14:11,15:$V5,17:$V6,19:$V7,20:$V8,21:[1,51],22:$V9,33:$Va},{24:52,40:$Vg},{24:54,40:$Vg},{12:56,27:55,33:$Va},{8:$Ve,26:57,33:[2,18]},o($Vc,[2,11]),o($Vc,[2,12]),o($Vc,$Vb),o($Vc,[2,13]),{12:58,33:$Va},{10:[2,23]},{10:[2,33]},{10:[2,15]},{12:59,33:$Va},{28:[1,60],33:[2,19]},{33:[2,17]},o($Vd,$V1,{5:61}),{10:[2,16]},{12:62,33:$Va},{6:$Vf,7:5,8:$V2,9:7,10:$V3,11:$V4,12:16,13:10,14:11,15:$V5,17:$V6,18:[1,63],19:$V7,20:$V8,22:$V9,33:$Va},{33:[2,20]},o($Vc,[2,14])],
defaultActions: {28:[2,27],29:[2,28],30:[2,29],31:[2,30],32:[2,31],33:[2,32],36:[2,21],37:[2,22],52:[2,23],53:[2,33],54:[2,15],57:[2,17],59:[2,16],62:[2,20]},
parseError: function parseError(str, hash) {
if (hash.recoverable) {
this.trace(str);
@ -621,48 +647,70 @@ performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START;
switch($avoiding_name_collisions) {
case 0:return 9;
case 0:return 10;
break;
case 1:/* skip whitespace */
case 1: return 38;
break;
case 2:/* skip comments */
case 2: return 39;
break;
case 3:return 10;
case 3: return 36;
break;
case 4:return 21;
case 4: return 37;
break;
case 5:return 22;
case 5:/* skip whitespace */
break;
case 6:return 18;
case 6:/* skip comments */
break;
case 7:return 16;
case 7:/* skip comments */
break;
case 8:return 14;
case 8:return 11;
break;
case 9:return 4;
case 9:return 19;
break;
case 10:return 20;
case 10:return 17;
break;
case 11:return 24;
case 11:return 20;
break;
case 12:return 28;
case 12:return 21;
break;
case 13:return 27;
case 13:return 18;
break;
case 14:return 30;
case 14:return 29;
break;
case 15:return 29;
case 15:return 30;
break;
case 16:return 31;
case 16:return 25;
break;
case 17:return 6;
case 17:return 22;
break;
case 18:return 'INVALID';
case 18:return 15;
break;
case 19:return 4;
break;
case 20:return 28;
break;
case 21:return 10;
break;
case 22:return 33;
break;
case 23:return 34;
break;
case 24:return 35;
break;
case 25:return 36;
break;
case 26:return 37;
break;
case 27:return 40;
break;
case 28:return 6;
break;
case 29:return 'INVALID';
break;
}
},
rules: [/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:participant\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:,)/i,/^(?:[^\->:\n,]+)/i,/^(?:--)/i,/^(?:-)/i,/^(?:>>)/i,/^(?:>)/i,/^(?:[^#\n]+)/i,/^(?:$)/i,/^(?:.)/i],
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18],"inclusive":true}}
rules: [/^(?:[\n]+)/i,/^(?:[\-][x])/i,/^(?:[\-][\-][x])/i,/^(?:[\-][>][>])/i,/^(?:[\-][\-][>][>])/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:participant\b)/i,/^(?:opt\b)/i,/^(?:loop\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\->:\n,;]+)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?::[^#\n;]+)/i,/^(?:$)/i,/^(?:.)/i],
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29],"inclusive":true}}
});
return lexer;
})();

View File

@ -41,9 +41,21 @@ exports.clear = function(){
};
exports.LINETYPE = {
SOLID : 0,
DOTTED : 1,
NOTE : 2
SOLID : 0,
DOTTED : 1,
NOTE : 2,
SOLID_CROSS : 3,
DOTTED_CROSS: 4,
SOLID_OPEN : 5,
DOTTED_OPEN : 6,
LOOP_START : 10,
LOOP_END : 11,
ALT_START : 12,
ALT_ELSE : 13,
ALT_END : 14,
OPT_START : 15,
OPT_END : 16
};
exports.ARROWTYPE = {
@ -61,10 +73,60 @@ exports.addNote = function (actor, placement, message){
var note = {actor:actor, placement: placement, message:message};
notes.push(note);
messages.push({from:actor, to:actor, message:message, type:exports.LINETYPE.NOTE});
messages.push({from:actor, to:actor, message:message, type:exports.LINETYPE.NOTE, placement: placement});
};
exports.parseError = function(err, hash) {
console.log('Syntax error:' + err);
};
exports.apply = function(param){
if(param instanceof Array ){
param.forEach(function(item){
exports.apply(item);
});
} else {
// console.log(param);
switch(param.type){
case 'addActor':
exports.addActor(param.actor, param.actor, param.actor);
break;
case 'addNote':
exports.addNote(param.actor,param.placement, param.text);
break;
case 'addMessage':
exports.addSignal(param.from, param.to, param.msg, param.signalType);
break;
case 'loopStart':
//console.log('Loop text: ',param.loopText);
exports.addSignal(undefined, undefined, param.loopText, param.signalType);
//yy.addSignal(undefined, undefined, $2, yy.LINETYPE.LOOP_START);
break;
case 'loopEnd':
exports.addSignal(undefined, undefined, undefined, param.signalType);
break;
case 'optStart':
//console.log('Loop text: ',param.loopText);
exports.addSignal(undefined, undefined, param.optText, param.signalType);
//yy.addSignal(undefined, undefined, $2, yy.LINETYPE.LOOP_START);
break;
case 'optEnd':
exports.addSignal(undefined, undefined, undefined, param.signalType);
break;
case 'altStart':
//console.log('Loop text: ',param.loopText);
exports.addSignal(undefined, undefined, param.altText, param.signalType);
//yy.addSignal(undefined, undefined, $2, yy.LINETYPE.LOOP_START);
break;
case 'else':
exports.addSignal(undefined, undefined, param.altText, param.signalType);
break;
case 'altEnd':
exports.addSignal(undefined, undefined, undefined, param.signalType);
break;
}
// console.log('xxx',param);
}
};

View File

@ -2,28 +2,9 @@
* Created by knut on 14-11-18.
*/
var sq = require('./parser/sequenceDiagram').parser;
var sd = require('./sequenceRenderer');
//console.log(sq.parse('a12:d12\na24:d24'));
str = 'a12:d12\n\na24:d24';
//console.log(str);
//console.log(sq.parse(str));
//console.log(sq.parse('[]\n[]'));
str = 'bfs:queue\n\nbfs3:queue\n';
str = str + 'bfs:message=someNode.setLevel\n';
str = str + 'bfs:message2=someNode.setLevel2';
//console.log(str);
//console.log(sq.parse(str));
str = 'bfs:BFS\n';
str = str + 'someNode:SomeNode\n';
str = str + 'bfs:queue.new\n';
str = str + 'bfs:someNode.setLevel';
//console.log(str);
//console.log(sq.parse(str));
var str;
describe('when parsing a sequenceDiagram',function() {
var parseError;
beforeEach(function () {
@ -38,25 +19,655 @@ describe('when parsing a sequenceDiagram',function() {
it('it should handle a sequenceDiagram defintion', function () {
str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n' +
'Alice->Bob:Hello Bob, how are you?\n' +
'Note right of Bob: Bob thinks\n' +
'Bob-->Alice: I am good thanks!\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice).ToBdescription = 'Alice';
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
//console.log('actors');
//console.log(actors);
var messages = sq.yy.getMessages();
expect(messages.length).toBe(3);
//console.log('messages');
expect(messages[0].from).toBe('Alice');
expect(messages[2].from).toBe('Bob');
});
it('it should space in actor names', function () {
str = 'sequenceDiagram\n' +
'Alice->Bob:Hello Bob, how are - you?\n' +
'Bob-->Alice: I am good thanks!\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
expect(messages.length).toBe(2);
expect(messages[0].from).toBe('Alice');
expect(messages[1].from).toBe('Bob');
});
it('it should handle in async messages', function () {
var str = 'sequenceDiagram\n' +
'Alice-xBob:Hello Bob, how are you?\n';
sq.parse(str);
var actors = sq.yy.getActors();
//console.log(actors);
expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob');
var messages = sq.yy.getMessages();
expect(messages.length).toBe(1);
expect(messages[0].type).toBe(sq.yy.LINETYPE.SOLID_CROSS);
});
it('it should handle in async dotted messages', function () {
var str = 'sequenceDiagram\n' +
'Alice--xBob:Hello Bob, how are you?\n';
sq.parse(str);
var actors = sq.yy.getActors();
//console.log(actors);
expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob');
var messages = sq.yy.getMessages();
expect(messages.length).toBe(1);
expect(messages[0].type).toBe(sq.yy.LINETYPE.DOTTED_CROSS);
});
it('it should handle in arrow messages', function () {
var str = 'sequenceDiagram\n' +
'Alice->>Bob:Hello Bob, how are you?\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob');
var messages = sq.yy.getMessages();
//console.log(messages);
expect(messages.length).toBe(1);
expect(messages[0].type).toBe(sq.yy.LINETYPE.SOLID);
});
it('it should handle in arrow messages', function () {
var str = 'sequenceDiagram\n' +
'Alice-->>Bob:Hello Bob, how are you?\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
expect(actors.Bob.description).toBe('Bob');
var messages = sq.yy.getMessages();
//console.log(messages);
expect(messages.length).toBe(1);
expect(messages[0].type).toBe(sq.yy.LINETYPE.DOTTED);
});
it('it should handle comments in a sequenceDiagram', function () {
str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n'+
'%% Comment\n' +
'Note right of Bob: Bob thinks\n' +
'Bob-->Alice: I am good thanks!\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
expect(messages.length).toBe(3);
expect(messages[0].from).toBe('Alice');
expect(messages[2].from).toBe('Bob');
});
it('it should handle new lines in a sequenceDiagram', function () {
str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' +
'Note right of Bob: Bob thinks\n' +
'Bob-->Alice: I am good thanks!\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
expect(messages.length).toBe(3);
expect(messages[0].from).toBe('Alice');
expect(messages[2].from).toBe('Bob');
});
it('it should handle one leading space in lines in a sequenceDiagram', function () {
str = 'sequenceDiagram\n' +
' Alice->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' +
'Note right of Bob: Bob thinks\n' +
'Bob-->Alice: I am good thanks!\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
expect(messages.length).toBe(3);
expect(messages[0].from).toBe('Alice');
expect(messages[2].from).toBe('Bob');
});
it('it should handle several leading spaces in lines in a sequenceDiagram', function () {
str = 'sequenceDiagram\n' +
' Alice->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' +
'Note right of Bob: Bob thinks\n' +
'Bob-->Alice: I am good thanks!\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
expect(messages.length).toBe(3);
expect(messages[0].from).toBe('Alice');
expect(messages[2].from).toBe('Bob');
});
it('it should handle several leading spaces in lines in a sequenceDiagram', function () {
str = 'sequenceDiagram\n'+
'participant Alice\n'+
'participant Bob\n'+
'Alice->John: Hello John, how are you?\n'+
' loop Healthcheck\n'+
'John->John: Fight against hypochondria\n'+
' end\n'+
'Note right of John: Rational thoughts<br/>prevail...\n'+
' John-->Alice: Great!\n'+
' John->Bob: How about you?\n'+
'Bob-->John: Jolly good!\n';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
expect(messages.length).toBe(8);
expect(messages[0].from).toBe('Alice');
expect(messages[2].from).toBe('John');
});
it('it should handle loop statements a sequenceDiagram', function () {
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' +
'Note right of Bob: Bob thinks\n' +
'loop Multiple happy responses\n\n' +
'Bob-->Alice: I am good thanks!\n' +
'end';
sq.parse(str);
var actors = sq.yy.getActors();
//console.log(actors);
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
//console.log(messages);
expect(messages.length).toBe(5);
expect(messages[0].from).toBe('Alice');
expect(messages[1].from).toBe('Bob');
});
it('it should handle opt statements a sequenceDiagram', function () {
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' +
'Note right of Bob: Bob thinks\n' +
'opt Perhaps a happy response\n\n' +
'Bob-->Alice: I am good thanks!\n' +
'end';
sq.parse(str);
var actors = sq.yy.getActors();
//console.log(actors);
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
//console.log(messages);
expect(messages.length).toBe(5);
expect(messages[0].from).toBe('Alice');
expect(messages[1].from).toBe('Bob');
});
it('it should handle opt statements a sequenceDiagram', function () {
var str = 'sequenceDiagram;Alice->Bob: Hello Bob, how are you?;opt Perhaps a happy response;Bob-->Alice: I am good thanks!;end;';
sq.parse(str);
var actors = sq.yy.getActors();
//console.log(actors);
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
//console.log(messages);
expect(messages.length).toBe(4);
expect(messages[0].from).toBe('Alice');
expect(messages[1].type).toBe(sq.yy.LINETYPE.OPT_START);
expect(messages[2].from).toBe('Bob');
});
it('it should handle alt statements a sequenceDiagram', function () {
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n\n' +
'%% Comment\n' +
'Note right of Bob: Bob thinks\n' +
'alt isWell\n\n' +
'Bob-->Alice: I am good thanks!\n' +
'else isSick\n' +
'Bob-->Alice: Feel sick...\n' +
'end';
sq.parse(str);
var actors = sq.yy.getActors();
expect(actors.Alice.description).toBe('Alice');
actors.Bob.description = 'Bob';
var messages = sq.yy.getMessages();
//console.log(messages);
expect(messages.length).toBe(7);
expect(messages[0].from).toBe('Alice');
expect(messages[1].from).toBe('Bob');
});});
describe('when checking the bounds in a sequenceDiagram',function() {
var parseError, _d3, conf;
beforeEach(function () {
sq.yy = require('./sequenceDb');
sq.yy.clear();
parseError = function(err, hash) {
console.log('Syntax error:' + err);
console.log(hash);
};
sq.yy.parseError = parseError;
conf = {
diagramMarginX:50,
diagramMarginY:10,
actorMargin:50,
width:150,
// Height of actor boxes
height:65,
boxMargin:10,
messageMargin:40,
boxTextMargin:15,
noteMargin:25
};
sd.setConf(conf);
});
it('it should handle a simple bound call', function () {
sd.bounds.init();
sd.bounds.insert(100,100,200,200);
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(100);
expect(bounds.starty).toBe(100);
expect(bounds.stopx ).toBe(200);
expect(bounds.stopy ).toBe(200);
});
it('it should handle an expanding bound', function () {
sd.bounds.init();
sd.bounds.insert(100,100,200,200);
sd.bounds.insert(25,50,300,400);
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(25);
expect(bounds.starty).toBe(50);
expect(bounds.stopx ).toBe(300);
expect(bounds.stopy ).toBe(400);
});
it('it should handle inserts within the bound without changing the outer bounds', function () {
sd.bounds.init();
sd.bounds.insert(100,100,200,200);
sd.bounds.insert(25,50,300,400);
sd.bounds.insert(125,150,150,200);
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(25);
expect(bounds.starty).toBe(50);
expect(bounds.stopx ).toBe(300);
expect(bounds.stopy ).toBe(400);
});
it('it should handle a loop without expanding the area', function () {
sd.bounds.init();
sd.bounds.insert(25,50,300,400);
sd.bounds.verticalPos = 150;
sd.bounds.newLoop();
sd.bounds.insert(125,150,150,200);
var loop = sd.bounds.endLoop();
expect(loop.startx).toBe(125-conf.boxMargin);
expect(loop.starty).toBe(150-conf.boxMargin);
expect(loop.stopx ).toBe(150+conf.boxMargin);
expect(loop.stopy ).toBe(200+conf.boxMargin);
// Check bounds of first loop
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(25);
expect(bounds.starty).toBe(50);
expect(bounds.stopx ).toBe(300);
expect(bounds.stopy ).toBe(400);
});
it('it should handle multiple loops withtout expanding the bounds', function () {
sd.bounds.init();
sd.bounds.insert(100,100,1000,1000);
sd.bounds.verticalPos = 200;
sd.bounds.newLoop();
sd.bounds.newLoop();
sd.bounds.insert(200,200,300,300);
// Check bounds of first loop
var loop = sd.bounds.endLoop();
expect(loop.startx).toBe(200-conf.boxMargin);
expect(loop.starty).toBe(200-conf.boxMargin);
expect(loop.stopx ).toBe(300+conf.boxMargin);
expect(loop.stopy ).toBe(300+conf.boxMargin);
// Check bounds of second loop
loop = sd.bounds.endLoop();
expect(loop.startx).toBe(200-2*conf.boxMargin);
expect(loop.starty).toBe(200-2*conf.boxMargin);
expect(loop.stopx ).toBe(300+2*conf.boxMargin);
expect(loop.stopy ).toBe(300+2*conf.boxMargin);
// Check bounds of first loop
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(100);
expect(bounds.starty).toBe(100);
expect(bounds.stopx ).toBe(1000);
expect(bounds.stopy ).toBe(1000);
});
it('it should handle a loop that expands the area', function () {
sd.bounds.init();
sd.bounds.insert(100,100,200,200);
sd.bounds.verticalPos = 200;
sd.bounds.newLoop();
sd.bounds.insert(50,50,300,300);
var loop = sd.bounds.endLoop();
expect(loop.startx).toBe(50 - conf.boxMargin);
expect(loop.starty).toBe(50 - conf.boxMargin);
expect(loop.stopx ).toBe(300 + conf.boxMargin);
expect(loop.stopy ).toBe(300 + conf.boxMargin);
// Check bounds after the loop
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(loop.startx);
expect(bounds.starty).toBe(loop.starty);
expect(bounds.stopx ).toBe(loop.stopx);
expect(bounds.stopy ).toBe(loop.stopy);
});
});
describe('when rendering a sequenceDiagram',function() {
var parseError, _d3, conf;
beforeEach(function () {
sq.yy = require('./sequenceDb');
sq.yy.clear();
parseError = function(err, hash) {
console.log('Syntax error:' + err);
console.log(hash);
};
sq.yy.parseError = parseError;
function newD3() {
var o = {
append: function (type) {
return newD3();
},
attr: function (key, val) {
return this;
},
style: function (key, val) {
return this;
},
text: function (txt) {
return this;
},
0:{
0: {
getBBox: function () {
return {
height: 10,
width: 20
};
}
}
}
};
return o;
}
var _d3 = {
select:function(){
return new newD3();
}
};
d3 = _d3;
conf = {
diagramMarginX:50,
diagramMarginY:10,
actorMargin:50,
width:150,
// Height of actor boxes
height:65,
boxMargin:10,
messageMargin:40,
boxTextMargin:15,
noteMargin:25
};
sd.setConf(conf);
});
it('it should handle one actor', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'participant Alice\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx ).toBe( conf.width);
expect(bounds.stopy ).toBe(conf.height);
});
it('it should handle one actor and a note', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'participant Alice\n' +
'Note left of Alice: Alice thinks\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(-(conf.width/2)-(conf.actorMargin/2));
expect(bounds.starty).toBe(0);
expect(bounds.stopx ).toBe( conf.width );
// 10 comes from mock of text height
expect(bounds.stopy ).toBe( conf.height + conf.boxMargin + 2*conf.noteMargin +10);
});
it('it should handle one actor and a note to the right', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'participant Alice\n' +
'Note right of Alice: Alice thinks\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx ).toBe( (conf.width/2) + (conf.actorMargin/2) + conf.width);
// 10 comes from mock of text height
expect(bounds.stopy ).toBe( conf.height + conf.boxMargin + 2*conf.noteMargin +10);
});
it('it should handle two actors', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx ).toBe(conf.width*2 + conf.actorMargin);
expect(bounds.stopy ).toBe(0 + conf.messageMargin + conf.height);
});
it('it should draw two actors and two messages', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n'+
'Bob->Alice: Fine!\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx ).toBe(0 + conf.width*2 + conf.actorMargin);
expect(bounds.stopy ).toBe(0 + 2*conf.messageMargin + conf.height);
});
it('it should draw two actors notes to the right', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n'+
'Note right of Bob: Bob thinks\n' +
'Bob->Alice: Fine!\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
var expStopX = conf.actorMargin +conf.width+ (conf.width/2) + conf.noteMargin + conf.width;
expect(bounds.stopx ).toBe(expStopX);
expect(bounds.stopy ).toBe(2*conf.messageMargin + conf.height + conf.boxMargin + 10+ 2*conf.noteMargin);
});
it('it should draw two actors notes to the left', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n'+
'Note left of Alice: Bob thinks\n' +
'Bob->Alice: Fine!\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe( -(conf.width/2)-(conf.actorMargin/2));
expect(bounds.starty).toBe(0);
expect(bounds.stopx ).toBe( conf.width*2 + conf.actorMargin);
expect(bounds.stopy ).toBe( 2*conf.messageMargin + conf.height + conf.boxMargin +10+ 2*conf.noteMargin);
});
it('it should draw two loops', function () {
sd.bounds.init();
var str = 'sequenceDiagram\n' +
'Alice->Bob: Hello Bob, how are you?\n'+
'loop Cheers\n' +
'Bob->Alice: Fine!\n' +
'end\n';
sq.parse(str);
sd.draw(str,'tst');
var bounds = sd.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx ).toBe(0 + conf.width*2 + conf.actorMargin);
expect(bounds.stopy ).toBe(0 + 2*conf.messageMargin + conf.height + 3*conf.boxMargin + conf.boxTextMargin);
});
});

View File

@ -5,6 +5,114 @@
var sq = require('./parser/sequenceDiagram').parser;
sq.yy = require('./sequenceDb');
var svgDraw = require('./svgDraw');
var conf = {
diagramMarginX:50,
diagramMarginY:10,
// Margin between actors
actorMargin:50,
// Width of actor moxes
width:150,
// Height of actor boxes
height:65,
// Margin around loop boxes
boxMargin:10,
boxTextMargin:5,
noteMargin:10,
// Space between messages
messageMargin:35
};
exports.bounds = {
data:{
startx:undefined,
stopx :undefined,
starty:undefined,
stopy :undefined,
},
verticalPos:0,
list: [],
init : function(){
this.list = [];
this.data = {
startx:undefined,
stopx :undefined,
starty:undefined,
stopy :undefined,
};
this.verticalPos =0;
},
updateVal : function (obj,key,val,fun){
if(typeof obj[key] === 'undefined'){
obj[key] = val;
}else{
obj[key] = fun(val,obj[key]);
}
},
updateLoops:function(startx,starty,stopx,stopy){
var _self = this;
var cnt = 0;
this.list.forEach(function(loop){
cnt++;
// The loop list is a stack so the biggest margins in the beginning of the list
var n = _self.list.length-cnt+1;
_self.updateVal(loop, 'startx',startx - n*conf.boxMargin, Math.min);
_self.updateVal(loop, 'starty',starty - n*conf.boxMargin, Math.min);
_self.updateVal(loop, 'stopx' ,stopx + n*conf.boxMargin, Math.max);
_self.updateVal(loop, 'stopy' ,stopy + n*conf.boxMargin, Math.max);
_self.updateVal(exports.bounds.data,'startx',startx - n*conf.boxMargin ,Math.min);
_self.updateVal(exports.bounds.data,'starty',starty - n*conf.boxMargin ,Math.min);
_self.updateVal(exports.bounds.data,'stopx' ,stopx + n*conf.boxMargin ,Math.max);
_self.updateVal(exports.bounds.data,'stopy' ,stopy + n*conf.boxMargin ,Math.max);
});
},
insert:function(startx,starty,stopx,stopy){
var _startx, _starty, _stopx, _stopy;
_startx = Math.min(startx,stopx);
_stopx = Math.max(startx,stopx);
_starty = Math.min(starty,stopy);
_stopy = Math.max(starty,stopy);
this.updateVal(exports.bounds.data,'startx',_startx,Math.min);
this.updateVal(exports.bounds.data,'starty',_starty,Math.min);
this.updateVal(exports.bounds.data,'stopx' ,_stopx ,Math.max);
this.updateVal(exports.bounds.data,'stopy' ,_stopy ,Math.max);
this.updateLoops(_startx,_starty,_stopx,_stopy);
},
newLoop:function(title){
this.list.push({startx:undefined,starty:this.verticalPos,stopx:undefined,stopy:undefined, title:title});
},
endLoop:function(){
var loop = this.list.pop();
//loop.stopy = exports.bounds.getVerticalPos();
return loop;
},
addElseToLoop:function(message){
var loop = this.list.pop();
loop.elsey = exports.bounds.getVerticalPos();
loop.elseText = message;
this.list.push(loop);
},
bumpVerticalPos:function(bump){
this.verticalPos = this.verticalPos + bump;
this.data.stopy = this.verticalPos;
},
getVerticalPos:function(){
return this.verticalPos;
},
getBounds:function(){
return this.data;
}
};
/**
* Draws an actor in the diagram with the attaced line
@ -12,51 +120,124 @@ sq.yy = require('./sequenceDb');
* @param pos The position if the actor in the liost of actors
* @param description The text in the box
*/
var drawNote = function(elem, startX, verticalPos, msg){
var insertLinebreaks = function (d) {
var el = d3.select(this);
var words = d.split(' ');
el.text('');
for (var i = 0; i < words.length; i++) {
var tspan = el.append('tspan').text(words[i]);
if (i > 0)
tspan.attr('x', 0).attr('dy', '15');
}
};
var drawNote = function(elem, startx, verticalPos, msg){
var rect = svgDraw.getNoteRect();
rect.x = startx;
rect.y = verticalPos;
rect.width = conf.width;
rect.class = 'note';
var g = elem.append("g");
var rectElem = g.append("rect")
.attr("x", startX + 25)
.attr("y", verticalPos -25)
.attr("fill", '#EDF2AE')
.attr("stroke", '#666')
.attr("width", 150)
.attr("height", 100)
.attr("rx", 0)
.attr("ry", 0);
var textElem = g.append("text")
.attr("x", startX + 10)
.attr("y", verticalPos - 15)
.style("text-anchor", "start");
msg.message.split('<br>').forEach(function(rowText){
textElem.append("tspan")
.attr("x", startX + 35)
.attr("dy", '1em')
.text(rowText);
});
var rectElem = svgDraw.drawRect(g, rect);
console.log('textElem.height');
console.log(textElem[0][0].getBBox());
rectElem.attr('height',textElem[0][0].getBBox().height+20);
//console.log(textElem.getBBox().height);
var textObj = svgDraw.getTextObj();
textObj.x = startx;
textObj.y = verticalPos+conf.noteMargin;
textObj.textMargin = conf.noteMargin;
textObj.dy = '1em';
textObj.text = msg.message;
textObj.class = 'noteText';
//.text(msg.message + '\n' + msg.message)
var textElem = svgDraw.drawText(g,textObj);
var textHeight = textElem[0][0].getBBox().height;
exports.bounds.insert(startx, verticalPos, startx + conf.width, verticalPos + 2*conf.noteMargin + textHeight);
return verticalPos + textElem[0][0].getBBox().height - 10;
rectElem.attr('height',textHeight+ 2*conf.noteMargin);
exports.bounds.bumpVerticalPos(textHeight+ 2*conf.noteMargin);
};
/**
* Draws a message
* @param elem
* @param startx
* @param stopx
* @param verticalPos
* @param txtCenter
* @param msg
*/
var drawMessage = function(elem, startx, stopx, verticalPos, msg){
var g = elem.append("g");
var txtCenter = startx + (stopx-startx)/2;
var textElem = g.append("text") // text label for the x axis
.attr("x", txtCenter)
.attr("y", verticalPos - 7)
.style("text-anchor", "middle")
.attr("class", "messageText")
.text(msg.message);
var textWidth = textElem[0][0].getBBox().width;
var line;
if(startx===stopx){
line = g.append("path")
.attr('d', 'M ' +startx+ ','+verticalPos+' C ' +(startx+60)+ ','+(verticalPos-10)+' ' +(startx+60)+ ',' +
(verticalPos+30)+' ' +startx+ ','+(verticalPos+20));
exports.bounds.bumpVerticalPos(30);
var dx = Math.max(textWidth/2,100);
exports.bounds.insert(startx-dx, exports.bounds.getVerticalPos() -10, stopx+dx, exports.bounds.getVerticalPos());
}else{
line = g.append("line");
line.attr("x1", startx);
line.attr("y1", verticalPos);
line.attr("x2", stopx);
line.attr("y2", verticalPos);
exports.bounds.insert(startx, exports.bounds.getVerticalPos() -10, stopx, exports.bounds.getVerticalPos());
}
//Make an SVG Container
//Draw the line
if (msg.type === sq.yy.LINETYPE.DOTTED || msg.type === sq.yy.LINETYPE.DOTTED_CROSS || msg.type === sq.yy.LINETYPE.DOTTED_OPEN) {
line.style("stroke-dasharray", ("3, 3"));
line.attr("class", "messageLine1");
}
else {
line.attr("class", "messageLine0");
}
line.attr("stroke-width", 2);
line.attr("stroke", "black");
line.style("fill", "none"); // remove any fill colour
if (msg.type === sq.yy.LINETYPE.SOLID || msg.type === sq.yy.LINETYPE.DOTTED){
line.attr("marker-end", "url(#arrowhead)");
}
if (msg.type === sq.yy.LINETYPE.SOLID_CROSS || msg.type === sq.yy.LINETYPE.DOTTED_CROSS){
line.attr("marker-end", "url(#crosshead)");
}
};
module.exports.drawActors = function(diagram, actors, actorKeys){
var i;
// Draw the actors
for(i=0;i<actorKeys.length;i++){
var key = actorKeys[i];
// Add some rendering data to the object
actors[key].x = i*conf.actorMargin +i*conf.width;
actors[key].y = 0;
actors[key].width = conf.diagramMarginY;
actors[key].height = conf.diagramMarginY;
// Draw the box with the attached line
svgDraw.drawActor(diagram, actors[key].x, actors[key].description, conf);
exports.bounds.insert(actors[key].x, 0, actors[key].x + conf.width, conf.height);
}
// Add a margin between the actor boxes and the first arrow
//exports.bounds.bumpVerticalPos(conf.height+conf.messageMargin);
exports.bounds.bumpVerticalPos(conf.height);
};
module.exports.setConf = function(cnf){
conf = cnf;
};
/**
* Draws a flowchart in the tag with id: id based on the graph definition in text.
* @param text
@ -64,160 +245,101 @@ var drawNote = function(elem, startX, verticalPos, msg){
*/
module.exports.draw = function (text, id) {
sq.yy.clear();
sq.parse(text);
// Intial config for margins etc
var startMargin = 50;
var margin = 50;
var width = 150;
var height = 65;
var yStartMargin = 10;
//console.log(text);
sq.parse(text+'\n');
exports.bounds.init();
var diagram = d3.select('#'+id);
/**
* Draws an actor in the diagram with the attaced line
* @param center - The center of the the actor
* @param pos The position if the actor in the liost of actors
* @param description The text in the box
*/
var drawActor = function(elem, center, pos, description){
var g = elem.append("g");
g.append("line")
.attr("x1", center)
.attr("y1", yStartMargin)
.attr("x2", center)
.attr("y2", 2000)
.attr("stroke-width", '0.5px')
.attr("stroke", '#999');
g.append("rect")
.attr("x", startMargin + pos*margin +i*150)
.attr("y", yStartMargin)
.attr("fill", '#eaeaea')
.attr("stroke", '#666')
.attr("width", width)
.attr("height", height)
.attr("rx", 3)
.attr("ry", 3);
g.append("text") // text label for the x axis
.attr("x", startMargin + pos*margin +i*width + 75)
.attr("y", yStartMargin+37.5)
.style("text-anchor", "middle")
.text(description)
;
};
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*/
var insertArrowHead = function(elem){
elem.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("refX", 5) /*must be smarter way to calculate shift*/
.attr("refY", 2)
.attr("markerWidth", 6)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M 0,0 V 4 L6,2 Z"); //this is actual shape for arrowhead
};
var drawMessage = function(elem, startx, stopx, verticalPos, txtCenter, msg){
var g = elem.append("g");
//Make an SVG Container
//Draw the line
if(msg.type !== 2) {
if (msg.type === 1) {
g.append("line")
.attr("x1", startx)
.attr("y1", verticalPos)
.attr("x2", stopx)
.attr("y2", verticalPos)
.attr("stroke-width", 2)
.attr("stroke", "black")
.style("stroke-dasharray", ("3, 3"))
.attr("class", "link")
.attr("marker-end", "url(#arrowhead)");
//.attr("d", diagonal);
}
else {
g.append("line")
.attr("x1", startx)
.attr("y1", verticalPos)
.attr("x2", stopx)
.attr("y2", verticalPos)
.attr("stroke-width", 2)
.attr("stroke", "black")
.attr("class", "link")
.attr("marker-end", "url(#arrowhead)");
//.attr("d", diagonal);
}
g.append("text") // text label for the x axis
.attr("x", txtCenter)
.attr("y", verticalPos - 10)
.style("text-anchor", "middle")
.text(msg.message);
}
else{
g.append("text") // text label for the x axis
.attr("x", txtCenter)
.attr("y", verticalPos - 10)
.style("text-anchor", "middle")
.text(msg.message);
}
};
var startx;
var stopx;
// Fetch data from the parsing
var actors = sq.yy.getActors();
var actorKeys = sq.yy.getActorKeys();
var messages = sq.yy.getMessages();
var i, maxX = 0;
// Draw the actors
for(i=0;i<actorKeys.length;i++){
var key = actorKeys[i];
// Add some rendering data to the object
actors[key].x = startMargin + i*margin +i*150;
actors[key].y = yStartMargin;
actors[key].width = yStartMargin;
actors[key].height = yStartMargin;
var center = actors[key].x + (width/2);
// Keep track of width for with setting on the svg
maxX = Math.max(maxX,actors[key].x);
// Draw the box with the attached line
drawActor(diagram, center,i, actors[key].description);
}
maxX = maxX + width;
module.exports.drawActors(diagram, actors, actorKeys);
// The arrow head definition is attached to the svg once
insertArrowHead(diagram);
svgDraw.insertArrowHead(diagram);
svgDraw.insertArrowCrossHead(diagram);
// Draw the messages/signals
var verticalPos = startMargin + 30;
messages.forEach(function(msg){
var loopData;
switch(msg.type){
case sq.yy.LINETYPE.NOTE:
exports.bounds.bumpVerticalPos(conf.boxMargin);
startx = actors[msg.from].x;
stopx = actors[msg.to].x;
if(msg.placement !== 0){
// Right of
drawNote(diagram, startx + (conf.width + conf.actorMargin)/2, exports.bounds.getVerticalPos(), msg);
}else{
// Left of
drawNote(diagram, startx - (conf.width + conf.actorMargin)/2, exports.bounds.getVerticalPos(), msg);
}
break;
case sq.yy.LINETYPE.LOOP_START:
exports.bounds.bumpVerticalPos(conf.boxMargin);
exports.bounds.newLoop(msg.message);
exports.bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
break;
case sq.yy.LINETYPE.LOOP_END:
loopData = exports.bounds.endLoop();
svgDraw.drawLoop(diagram, loopData,'loop', conf);
exports.bounds.bumpVerticalPos(conf.boxMargin);
break;
case sq.yy.LINETYPE.OPT_START:
exports.bounds.bumpVerticalPos(conf.boxMargin);
exports.bounds.newLoop(msg.message);
exports.bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
break;
case sq.yy.LINETYPE.OPT_END:
loopData = exports.bounds.endLoop();
svgDraw.drawLoop(diagram, loopData, 'opt', conf);
exports.bounds.bumpVerticalPos(conf.boxMargin);
break;
case sq.yy.LINETYPE.ALT_START:
exports.bounds.bumpVerticalPos(conf.boxMargin);
exports.bounds.newLoop(msg.message);
exports.bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
break;
case sq.yy.LINETYPE.ALT_ELSE:
//exports.drawLoop(diagram, loopData);
exports.bounds.bumpVerticalPos(conf.boxMargin);
loopData = exports.bounds.addElseToLoop(msg.message);
exports.bounds.bumpVerticalPos(conf.boxMargin);
break;
case sq.yy.LINETYPE.ALT_END:
loopData = exports.bounds.endLoop();
svgDraw.drawLoop(diagram, loopData,'alt', conf);
exports.bounds.bumpVerticalPos(conf.boxMargin);
break;
default:
exports.bounds.bumpVerticalPos(conf.messageMargin);
startx = actors[msg.from].x + conf.width/2;
stopx = actors[msg.to].x + conf.width/2;
drawMessage(diagram, startx, stopx, exports.bounds.getVerticalPos(), msg);
verticalPos = verticalPos + 40;
var startx = actors[msg.from].x + width/2;
var stopx = actors[msg.to].x + width/2;
var txtCenter = startx + (stopx-startx)/2;
if(msg.type === 2){
console.log('VP before:',verticalPos);
verticalPos = drawNote(diagram, startx, verticalPos, msg);
console.log('VP after:',verticalPos);
} else {
drawMessage(diagram, startx, stopx, verticalPos, txtCenter, msg);
// Keep track of width for with setting on the svg
maxX = Math.max(maxX,startx + 176);
}
});
diagram.attr("height", verticalPos + 40);
diagram.attr("width", maxX );
var box = exports.bounds.getBounds();
var height = box.stopy-box.starty+2*conf.diagramMarginY;
var width = box.stopx-box.startx+2*conf.diagramMarginX;
diagram.attr("height",height);
diagram.attr("width", width );
diagram.attr("viewBox", (box.startx-conf.diagramMarginX) + ' -' +conf.diagramMarginY + ' ' + width + ' ' + height);
};

View File

@ -0,0 +1,228 @@
/**
* Created by knut on 14-12-20.
*/
exports.drawRect = function(elem , rectData){
var rectElem = elem.append("rect");
rectElem.attr("x", rectData.x);
rectElem.attr("y", rectData.y);
rectElem.attr("fill", rectData.fill);
rectElem.attr("stroke", rectData.stroke);
rectElem.attr("width", rectData.width);
rectElem.attr("height", rectData.height);
rectElem.attr("rx", rectData.rx);
rectElem.attr("ry", rectData.ry);
if(typeof rectData.class !== 'undefined'){
rectElem.attr("class", rectData.class);
}
return rectElem;
};
exports.drawText = function(elem , textData){
var textElem = elem.append('text');
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
textElem.attr('fill', textData.fill);
textData.text.split(/<br\/?>/ig).forEach(function(rowText){
var span = textElem.append('tspan');
span.attr('x', textData.x +textData.textMargin);
span.attr('dy', textData.dy);
span.text(rowText);
});
if(typeof textData.class !== 'undefined'){
textElem.attr("class", textData.class);
}
return textElem;
};
exports.drawLabel = function(elem , txtObject){
var rectData = exports.getNoteRect();
rectData.x = txtObject.x;
rectData.y = txtObject.y;
rectData.width = 50;
rectData.height = 20;
rectData.fill = '#526e52';
rectData.stroke = 'none';
rectData.class = 'labelBox';
//rectData.color = 'white';
exports.drawRect(elem, rectData);
txtObject.y = txtObject.y + txtObject.labelMargin;
txtObject.x = txtObject.x + 0.5*txtObject.labelMargin;
txtObject.fill = 'white';
exports.drawText(elem, txtObject);
//return textElem;
};
/**
* Draws an actor in the diagram with the attaced line
* @param center - The center of the the actor
* @param pos The position if the actor in the liost of actors
* @param description The text in the box
*/
exports.drawActor = function(elem, left,description,conf){
var center = left + (conf.width/2);
var g = elem.append("g");
g.append("line")
.attr("x1", center)
.attr("y1", 5)
.attr("x2", center)
.attr("y2", 2000)
.attr("class", 'actor-line')
.attr("stroke-width", '0.5px')
.attr("stroke", '#999');
var rect = exports.getNoteRect();
rect.x = left;
rect.fill = '#eaeaea';
rect.width = conf.width;
rect.height = conf.height;
rect.class = 'actor';
rect.rx = 3;
rect.ry = 3;
exports.drawRect(g, rect);
g.append("text") // text label for the x axis
.attr("x", center)
.attr("y", (conf.height/2)+5)
.attr('class','actor')
.style("text-anchor", "middle")
.text(description)
;
};
/**
* Draws an actor in the diagram with the attaced line
* @param center - The center of the the actor
* @param pos The position if the actor in the list of actors
* @param description The text in the box
*/
exports.drawLoop = function(elem,bounds,labelText, conf){
var g = elem.append("g");
var drawLoopLine = function(startx,starty,stopx,stopy){
g.append("line")
.attr("x1", startx)
.attr("y1", starty)
.attr("x2", stopx )
.attr("y2", stopy )
.attr("stroke-width", 2)
.attr("stroke", "#526e52")
.attr('class','loopLine');
};
drawLoopLine(bounds.startx, bounds.starty, bounds.stopx , bounds.starty);
drawLoopLine(bounds.stopx , bounds.starty, bounds.stopx , bounds.stopy );
drawLoopLine(bounds.startx, bounds.stopy , bounds.stopx , bounds.stopy );
drawLoopLine(bounds.startx, bounds.starty, bounds.startx, bounds.stopy );
if(typeof bounds.elsey !== 'undefined'){
drawLoopLine(bounds.startx, bounds.elsey, bounds.stopx, bounds.elsey );
}
var txt = exports.getTextObj();
txt.text = labelText;
txt.x = bounds.startx;
txt.y = bounds.starty;
txt.labelMargin = 1.5 * conf.boxMargin;
txt.class = 'labelText';
txt.fill = 'white';
exports.drawLabel(g,txt);
txt = exports.getTextObj();
txt.text = '[ ' + bounds.title + ' ]';
txt.x = bounds.startx + (bounds.stopx - bounds.startx)/2;
txt.y = bounds.starty + 1.5 * conf.boxMargin;
txt.anchor = 'middle';
txt.class = 'loopText';
exports.drawText(g,txt);
if(typeof bounds.elseText !== 'undefined') {
txt.text = '[ ' + bounds.elseText + ' ]';
txt.y = bounds.elsey + 1.5 * conf.boxMargin;
exports.drawText(g, txt);
}
};
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*/
exports.insertArrowHead = function(elem){
elem.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("refX", 5)
.attr("refY", 2)
.attr("markerWidth", 6)
.attr("markerHeight", 4)
.attr("orient", "auto")
.append("path")
.attr("d", "M 0,0 V 4 L6,2 Z"); //this is actual shape for arrowhead
};
/**
* Setup arrow head and define the marker. The result is appended to the svg.
*/
exports.insertArrowCrossHead = function(elem){
var defs = elem.append("defs");
var marker = defs.append("marker")
.attr("id", "crosshead")
.attr("markerWidth", 15)
.attr("markerHeight", 8)
.attr("orient", "auto")
.attr("refX", 16)
.attr("refY", 4);
// The arrow
marker.append("path")
.attr("fill",'black')
.attr("stroke",'#000000')
.style("stroke-dasharray", ("0, 0"))
.attr("stroke-width",'1px')
.attr("d", "M 9,2 V 6 L16,4 Z");
// The cross
marker.append("path")
.attr("fill",'none')
.attr("stroke",'#000000')
.style("stroke-dasharray", ("0, 0"))
.attr("stroke-width",'1px')
.attr("d", "M 0,1 L 6,7 M 6,1 L 0,7")
; //this is actual shape for arrowhead
};
exports.getTextObj = function(){
var txt = {
x: 0,
y: 0,
'fill':'black',
'text-anchor': 'start',
style: '#666',
width: 100,
height: 100,
textMargin:0,
rx: 0,
ry: 0
};
return txt;
};
exports.getNoteRect = function(){
var rect = {
x: 0,
y: 0,
fill: '#EDF2AE',
stroke: '#666',
width: 100,
anchor:'start',
height: 100,
rx: 0,
ry: 0
};
return rect;
};

View File

@ -29,7 +29,9 @@ var init = function () {
// Check if previously processed
if(!element.getAttribute("data-processed")) {
element.setAttribute("data-processed", true);
} else continue;
} else {
continue;
}
var id;
@ -50,7 +52,6 @@ var init = function () {
switch(graphType){
case 'graph':
console.log('FC');
classes = flowRenderer.getClasses(txt, false);
flowRenderer.draw(txt, id, false);
utils.cloneCssStyles(element.firstChild, classes);
@ -64,7 +65,7 @@ var init = function () {
case 'sequenceDiagram':
seq.draw(txt,id);
// TODO - Get styles for sequence diagram
utils.cloneCssStyles(element.firstChild, classes);
utils.cloneCssStyles(element.firstChild, []);
break;
}
@ -90,28 +91,9 @@ var equals = function (val, variable){
return (val === variable);
}
};
if(typeof document !== 'undefined'){
/**
* Wait for coument loaded before starting the execution
*/
document.addEventListener('DOMContentLoaded', function(){
// Check presence of config object
if(typeof mermaid_config !== 'undefined'){
// Check if property startOnLoad is set
if(equals(true,mermaid_config.startOnLoad)){
init();
}
}
else{
// No config found, do autostart in this simple case
init();
}
}, false);
}
global.mermaid = {
startOnLoad:true,
init:function(){
init();
},
@ -121,4 +103,36 @@ global.mermaid = {
getParser:function(){
return flow.parser;
}
};
};
exports.contentLoaded = function(){
// Check state of start config mermaid namespece
//console.log('global.mermaid.startOnLoad',global.mermaid.startOnLoad);
//console.log('mermaid_config',mermaid_config);
if(global.mermaid.startOnLoad) {
// For backwards compatability reasons also check mermaid_config variable
if (typeof mermaid_config !== 'undefined') {
// Check if property startOnLoad is set
if (equals(true, mermaid_config.startOnLoad)) {
global.mermaid.init();
}
}
else {
// No config found, do autostart in this simple case
global.mermaid.init();
}
}
};
if(typeof document !== 'undefined'){
/**
* Wait for coument loaded before starting the execution
*/
document.addEventListener('DOMContentLoaded', function(){
exports.contentLoaded();
}, false);
}

View File

@ -6,35 +6,71 @@
*/
var rewire = require("rewire");
var utils = require("./utils");
var main = require("./main");
describe('when using main and ',function() {
describe('when detecting chart type ',function() {
var main;
//var main;
//var document;
//var window;
beforeEach(function () {
var MockBrowser = require('mock-browser').mocks.MockBrowser;
var mock = new MockBrowser();
delete global.mermaid_config;
// and in the run-code inside some object
document = mock.getDocument();
window = mock.getWindow();
});
it('should not call start anything with an empty document', function () {
mermaid_config ={startOnLoad : false};
it('should not start rendering with mermaid_config.startOnLoad set to false', function () {
main = rewire('./main');
spyOn(utils,'detectType');
expect(utils.detectType).not.toHaveBeenCalled();
});
it('should start something with a mermaid document', function () {
mermaid_config ={startOnLoad : false};
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(global.mermaid,'init');
//console.log(main);
main.contentLoaded();
expect(global.mermaid.init).not.toHaveBeenCalled();
});
it('should not start rendering with mermaid.startOnLoad set to false', function () {
main = rewire('./main');
mermaid.startOnLoad = false;
mermaid_config ={startOnLoad : true};
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(global.mermaid,'init');
main.contentLoaded();
expect(global.mermaid.init).not.toHaveBeenCalled();
});
it('should start rendering with both startOnLoad set', function () {
main = rewire('./main');
mermaid.startOnLoad = true;
mermaid_config ={startOnLoad : true};
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(global.mermaid,'init');
main.contentLoaded();
expect(global.mermaid.init).toHaveBeenCalled();
});
it('should start rendering with mermaid.startOnLoad set and no mermaid_config defined', function () {
main = rewire('./main');
mermaid.startOnLoad = true;
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(global.mermaid,'init');
main.contentLoaded();
expect(global.mermaid.init).toHaveBeenCalled();
});
it('should start rendering as a default with no changes performed', function () {
main = rewire('./main');
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
spyOn(utils,'detectType');
mermaid.init();
expect(utils.detectType).toHaveBeenCalled();
spyOn(global.mermaid,'init');
main.contentLoaded();
expect(global.mermaid.init).toHaveBeenCalled();
});
});

View File

@ -9,7 +9,6 @@
*/
module.exports.detectType = function(text,a){
if(text.match(/^\s*sequenceDiagram/)){
console.log('Detected sequenceDiagram syntax');
return "sequenceDiagram";
}
@ -36,15 +35,17 @@ module.exports.cloneCssStyles = function(svg, classes){
var usedStyles = "";
var sheets = document.styleSheets;
for (var i = 0; i < sheets.length; i++) {
// Only clone css from stylesheets intended for mermaid
if (sheets[i].title == 'mermaid') {
// Avoid multiple inclusion on pages with multiple graphs
if (sheets[i].title !== 'mermaid-svg-internal-css') {
var rules = sheets[i].cssRules;
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
if (typeof(rule.style) != "undefined") {
var elems = svg.querySelectorAll(rule.selectorText);
if (elems.length > 0) {
usedStyles += rule.selectorText + " { " + rule.style.cssText + " }\n";
if(rules !== null) {
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
if (typeof(rule.style) !== 'undefined') {
var elems = svg.querySelectorAll(rule.selectorText);
if (elems.length > 0) {
usedStyles += rule.selectorText + " { " + rule.style.cssText + " }\n";
}
}
}
}
@ -79,6 +80,8 @@ module.exports.cloneCssStyles = function(svg, classes){
s.setAttribute('type', 'text/css');
s.setAttribute('title', 'mermaid-svg-internal-css');
s.innerHTML = "/* <![CDATA[ */\n";
// Make this CSS local to this SVG
s.innerHTML += "#" + svg.id.trim() + " {\n";
if (defaultStyles !== "") {
s.innerHTML += defaultStyles;
}
@ -88,6 +91,7 @@ module.exports.cloneCssStyles = function(svg, classes){
if (embeddedStyles !== "") {
s.innerHTML += embeddedStyles;
}
s.innerHTML += "}\n";
s.innerHTML += "/* ]]> */\n";
svg.insertBefore(s, svg.firstChild);
}

View File

@ -208,7 +208,7 @@ describe('when cloning CSS ',function() {
expect(stylesToArray(svg)).toEqual([ '.node { stroke:#fff; stroke-width:1.5px; }', '.node { stroke: #eee; }', '.node-square { stroke: #bbb; }']);
});
it('should handle a default class together with stylesheet in document and classDefs', function () {
xit('should handle a default class together with stylesheet in document and classDefs', function () {
var svg = generateSVG();
addStyleToDocument('mermaid');
utils.cloneCssStyles(svg, { "default": { "styles": ["stroke:#fff","stroke-width:1.5px"] },

101
test/cli_test-output.js Normal file
View File

@ -0,0 +1,101 @@
var fs = require('fs')
, path = require('path')
var test = require('tape')
, async = require('async')
, clone = require('clone')
, rimraf = require('rimraf')
var mermaid = require('../lib')
var singleFile = {
files: ['test/fixtures/test.mermaid']
, outputDir: 'test/tmp/'
, phantomPath: './node_modules/.bin/phantomjs'
}
, multiFile = {
files: ['test/fixtures/test.mermaid', 'test/fixtures/test2.mermaid']
, outputDir: 'test/tmp/'
, phantomPath: './node_modules/.bin/phantomjs'
}
test('output of single png', function(t) {
t.plan(3)
var expected = ['test.mermaid.png']
opt = clone(singleFile)
opt.png = true
mermaid.process(opt.files, opt, function(code) {
t.equal(code, 0, 'has clean exit code')
verifyFiles(expected, opt.outputDir, t)
})
})
test('output of multiple png', function(t) {
t.plan(3)
var expected = ['test.mermaid.png', 'test2.mermaid.png']
opt = clone(multiFile)
opt.png = true
mermaid.process(opt.files, opt, function(code) {
t.equal(code, 0, 'has clean exit code')
verifyFiles(expected, opt.outputDir, t)
})
})
test('output of single svg', function(t) {
t.plan(3)
var expected = ['test.mermaid.svg']
opt = clone(singleFile)
opt.svg = true
mermaid.process(opt.files, opt, function(code) {
t.equal(code, 0, 'has clean exit code')
verifyFiles(expected, opt.outputDir, t)
})
})
test('output of multiple svg', function(t) {
t.plan(3)
var expected = ['test.mermaid.svg', 'test2.mermaid.svg']
opt = clone(multiFile)
opt.svg = true
mermaid.process(opt.files, opt, function(code) {
t.equal(code, 0, 'has clean exit code')
verifyFiles(expected, opt.outputDir, t)
})
})
function verifyFiles(expected, dir, t) {
async.each(
expected
, function(file, cb) {
filename = path.join(dir, path.basename(file))
fs.stat(filename, function(err, stat) {
cb(err)
})
}
, function(err) {
t.notOk(err, 'all files passed')
rimraf(dir, function(rmerr) {
t.notOk(rmerr, 'cleaned up')
t.end()
})
}
)
}

100
test/cli_test-parser.js Normal file
View File

@ -0,0 +1,100 @@
var test = require('tape')
, cliPath = '../lib/cli'
test('parses multiple files', function(t) {
t.plan(2)
var cli = require(cliPath)
, argv = ['example/file1.mermaid', 'file2.mermaid', 'file3.mermaid']
, expect = ['example/file1.mermaid', 'file2.mermaid', 'file3.mermaid']
cli.parse(argv, function(err, msg, opt) {
t.equal(opt.files.length, 3, 'should have 3 parameters')
t.deepEqual(opt.files, expect, 'should match expected values')
t.end()
})
})
test('defaults to png', function(t) {
t.plan(2)
var cli = require(cliPath)
, argv = ['example/file1.mermaid']
cli.parse(argv, function(err, msg, opt) {
t.ok(opt.png, 'png is set by default')
t.notOk(opt.svg, 'svg is not set by default')
t.end()
})
})
test('setting svg unsets png', function(t) {
t.plan(2)
var cli = require(cliPath)
, argv = ['example/file1.mermaid', '-s']
cli.parse(argv, function(err, msg, opt) {
t.ok(opt.svg, 'svg is set when requested')
t.notOk(opt.png, 'png is unset when svg is set')
t.end()
})
})
test('setting png and svg is allowed', function(t) {
t.plan(2)
var cli = require(cliPath)
, argv = ['example/file1.mermaid', '-s', '-p']
cli.parse(argv, function(err, msg, opt) {
t.ok(opt.png, 'png is set when requested')
t.ok(opt.svg, 'svg is set when requested')
t.end()
})
})
test('setting an output directory succeeds', function(t) {
t.plan(1)
var cli = require(cliPath)
, argv = ['-o', 'example/']
cli.parse(argv, function(err, msg, opt) {
t.equal(opt.outputDir, 'example/', 'output directory is set')
t.end()
})
})
test('setting an output directory incorrectly causes an error', function(t) {
t.plan(1)
var cli = require(cliPath)
, argv = ['-o']
cli.parse(argv, function(err) {
t.ok(err, 'an error is raised')
t.end()
})
})
test('a callback function is called after parsing', function(t) {
t.plan(2)
var cli = require(cliPath)
, argv = ['example/test.mermaid']
, expects = ['example/test.mermaid']
cli.parse(argv, function(err, msg, opts) {
t.ok(true, 'callback was called')
t.deepEqual(argv, opts.files, 'options are as expected')
t.end()
})
})

8
test/fixtures/sequence.mermaid vendored Normal file
View File

@ -0,0 +1,8 @@
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks
Bob-->Alice: I am good thanks!
Bob-->John the Long: How about you John?
Bob-->Alice: Checking with John...
Alice->John the Long: Yes... John, how are you?
John the Long-->Alice: Better than you!

5
test/fixtures/test.mermaid vendored Normal file
View File

@ -0,0 +1,5 @@
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;

7
test/fixtures/test2.mermaid vendored Normal file
View File

@ -0,0 +1,7 @@
graph LR;
A[Hard edge]-->|Link text|B(Round edge);
B-->C{Decision};
C-->|One|D[Result one];
C-->|Two|E[Result two];
classDef pink fill:#f9f,stroke:#333,stroke-width:4px;
class C pink;

81
test/seq.css Normal file
View File

@ -0,0 +1,81 @@
body {
background: #fcfcfe;
font-family: Helvetica;
}
.actor {
stroke: #CCCCFF;
fill: #ECECFF;
}
text.actor {
fill:black;
stroke:none;
font-family: Helvetica;
}
.actor-line {
stroke:grey;
}
.messageLine0 {
stroke-width:1.5;
stroke-dasharray: "2 2";
marker-end:"url(#arrowhead)";
stroke:black;
}
.messageLine1 {
stroke-width:1.5;
stroke-dasharray: "2 2";
stroke:black;
}
#arrowhead {
fill:black;
}
.messageText {
fill:black;
stroke:none;
font-family: 'trebuchet ms', verdana, arial;
font-size:14px;
}
.labelBox {
stroke: #CCCCFF;
fill: #ECECFF;
}
.labelText {
fill:black;
stroke:none;
font-family: 'trebuchet ms', verdana, arial;
}
.loopText {
fill:black;
stroke:none;
font-family: 'trebuchet ms', verdana, arial;
}
.loopLine {
stroke-width:2;
stroke-dasharray: "2 2";
marker-end:"url(#arrowhead)";
stroke: #CCCCFF;
}
.note {
stroke: #decc93;
stroke: #CCCCFF;
fill: #fff5ad;
}
.noteText {
fill:black;
stroke:none;
font-family: 'trebuchet ms', verdana, arial;
font-size:14px;
}

97
test/seq.html Normal file
View File

@ -0,0 +1,97 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<script src="../dist/mermaid.full.js"></script>
<link rel="stylesheet" href="seq.css"/>
<script>
var mermaid_config = {
startOnLoad:true
}
</script>
<script>
function apa(){
console.log('CLICKED');
}
</script>
</head>
<body>
<h1>No line breaks</h1>
<div class="mermaid">
sequenceDiagram;Alice->>Bob: Hello Bob, how are you?;Bob-->Bob: Hmmm?;Bob-->Alice: Ok;
</div>
<div class="mermaid">
sequenceDiagram;loop Daily query;Alice->>Bob: Hello Bob, how are you?;alt is sick;Bob->>Alice: Not so good :(;else is well;Bob->>Alice: Feeling fresh like a daisy;end;opt Extra response;Bob->>Alice: Thanks for asking;end;end;
</div>
<h1>Message types</h1>
<div class="mermaid">
sequenceDiagram
Alice->>Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--xAlice: I am good thanks!
Bob-xJohn: 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...
Alice->John: Yes... John, how are you?
</div>
<h1>Loops, alt and opt</h1>
<div class="mermaid">
sequenceDiagram
loop Daily query
Alice->>Bob: Hello Bob, how are you?
alt is sick
Bob->>Alice: Not so good :(
else is well
Bob->>Alice: Feeling fresh like a daisy
end
opt Extra response
Bob->>Alice: Thanks for asking
end
end
</div>
<h1>Message to self in loop</h1>
<div class="mermaid">
sequenceDiagram
participant Alice
participant Bob
Alice->>John: Hello John, how are you?
loop Healthcheck
John->>John: Fight against hypochondria
end
Note right of John: Rational thoughts<br/>prevail...
John-->>Alice: Great!
John->>Bob: How about you?
Bob-->>John: Jolly good!
</div>
<h1>Bounding test & async message to self</h1>
<div class="mermaid">
sequenceDiagram
participant Alice
participant Bob
participant John the Long
Alice->Bob: Hello Bob, how are you?
loop Outer loop
Note left of Alice: Bob thinks about <br/> things <br/> to think about
Bob-xBob: I am good thanks!
loop Inner loop
Bob->>John the Long: How about you John?
Note right of John the Long: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit.
end
end
Bob-->>Alice: Checking with John...
Alice->>John the Long: Yes... John, how are you?
John the Long-->>Alice: Super!
</div>
<br/>
</body>
</html>

View File

@ -4,73 +4,176 @@
<meta charset="UTF-8">
<script src="../dist/mermaid.full.js"></script>
<scrpt src="https://cdn.rawgit.com/knsv/mermaid/0.3.0/dist/mermaid.full.min.js"></scrpt>
<script>
var mermaid_config = {
startOnLoad:true
}
mermaid.startOnLoad=true;
</script>
<script>
function apa(){
console.log('CLICKED');
}
</script>
<style>
.cluster {
fill: #fcac93;
rx:4px;
stroke: grey;
}
</style>
<link rel="stylesheet" href="seq.css"/>
</head>
<body>
<h1>Shapes</h1>
Shape examples:
<pre>
graph TD;
sq[Square shape]-->ci((Circle shape));
od>Odd shape]---|Two line &lt;br>edge comment|ro;
od2>Really long text in an Odd shape]-->od3>Really long text with linebreak &lt;br>in an Odd shape];
di{Diamond is &lt;br> broken}-->ro(Rounded &lt;br>square &lt;br>shape);
%% Comments after double percent signs
di-->ro2(Rounded square shape);
e((Inner circle))-->f(,.?!+-*ز);
style e red;
</pre>
<div class="mermaid">
graph TD;
sq[Square shape]-->ci((Circle shape));
od>Odd shape]---|Two line<br>edge comment|ro;
od2>Really long text in an Odd shape]-->od3>Really long text with linebreak<br>in an Odd shape];
di{Diamond is <br/> broken}-->ro(Rounded<br>square<br>shape);
di-->ro2(Rounded square shape);
graph TD;
A-->B;
A-->C;
A-->D;
B-->D;
A-->|Link text|B
classDef default fill:#9f6,stroke:#333,stroke-width:2px;
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
class green B;
</div>
<h1>Sub graphs</h1>
<div class="mermaid">graph LR
subgraph old sys 1
a1(new client)-->b1(sys1 server)
oc1(Old client)-->b2
end
subgraph old sys 2
a2(new client)-->b2(sys2 server)
oc2(Old client)-->b2
end
subgraph old sys 3
a3(new client)-->b3(sys3 server)
end
subgraph New sys
a1
a2
a3
end
</div>
<div class="mermaid2">graph TB
subgraph one
a1-->a2
end
subgraph two
b1-->b2
end
subgraph three
c1-->c2
end
c1-->a2
</div>
<div class="mermaid2">graph TB
subgraph
sq[Square shape] -.-> ci((Circle shape))
od>Odd shape]-. Two line<br>edge comment .-> ro
di{Diamond with <br/> line break} ==> ro(Rounded<br>square<br>shape)
di-->ro2(Rounded square shape)
end
%% Notice that no text in shape are added here instead that is appended further down
subgraph Go go
e --> od3>Really long text with linebreak<br>in an Odd shape]
e((Inner / circle<br>and some odd <br>special characters)) --> f(,.?!+-*ز)
cyr[Cyrillic]-->cyr2((Circle shape Начало))
end
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
classDef orange fill:#f96,stroke:#333,stroke-width:4px,font-size:50%,font-style:bold;
class sq,e green
class di orange
</div>
<div class="mermaid2">
graph TB
subgraph
sq[Square shape]-->ci((Circle shape))
od>Odd shape]---|Two line<br>edge comment|ro
end
subgraph
od2>Really long text in an Odd shape]-->od3>Really long text with linebreak<br>in an Odd shape];
di{Diamond is <br/> broken}-->ro(Rounded<br>square<br>shape);
di-->ro2(Rounded square shape)
end
%% Comments after double percent signs
e((Inner / circle))-->f(,.?!+-*ز);
cyr[Cyrillic]-->cyr2((Circle shape Начало));
subgraph
e((Inner / circle))-->f(,.?!+-*ز);
cyr[Cyrillic]-->cyr2((Circle shape Начало));
A[Object foo,bar]-->B(Thing)
end
style e red;
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
class green sq
</div>
<div class="mermaid2">
graph LR;
A(Central Message Router);
B(R TD);
C(XYZ);
D(S Writer);
A-->|R TD Router|B;
B-->C;
C-->|XYZ Router|D;
</div>
<h1>Sequence diagrams (experimental)</h1>
<pre>
sequenceDiagram
participant John the Long
Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks
Note left of Bob: Bob thinks
Bob-->Alice: I am good thanks!
Bob-->John the Long: How about you John?
Note left of John the Long: Bob thinks
Bob-->Alice: Checking with John...
loop Multiple status checks
loog Naging
Alice->John the Long: Yes... John, how are you?
end
John the Long-->Alice: Better then you!
end
</pre>
<div class="mermaid">
<div class="mermaid2">
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Note right of Bob: Bob thinks about <br/> things <br/> to think about
Bob-->Alice: I am good thanks!
Bob-->John the Long: How about you John?
Bob-->Alice: Checking with John...
participant Alice
Note left of Alice: Bob thinks about <br/> things <br/> to think about
</div>
<div class="mermaid2">
sequenceDiagram
participant Alice
participant Bob
participant John
Alice->>Bob: Hello Bob, how are you?
Note left of Alice: Bob thinks about <br/> things <br/> to think about
Bob-->>Alice: I am good thanks!
loop Multiple status checks
Bob--xJohn: How about you John?
Note right of John: Bob thinks
end
Bob--xAlice: Checking with John...
Alice->John the Long: Yes... John, how are you?
John the Long-->Alice: Better then you!
John the Long-->Alice: Better then you!!
</div>
<div class="mermaid">
<div class="mermaid2">
graph LR;
A[Start]-->B{a = '1,2'};
B-->|True|C[test = 1];
B-->|False|Z[Store];
A[Start]-->B{a = '1,2'}
B-->|True|C[test = 1]
B-->|False|Z[Store]
C-->D{condition};
D-->|True|E[test = 2];
D-->|False|F[test = 3];
@ -96,7 +199,7 @@ graph LR;
a -- e;
}
</pre>
<div class="mermaid">
<div class="mermaid2">
digraph
{
a -> b -> c -- d -> e;