mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge pull request #932 from knsv/i931_code_standard
I931 code standard
This commit is contained in:
commit
4d1a34661e
18
.eslintrc.json
Normal file
18
.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"jsx": true
|
||||
},
|
||||
"sourceType": "module"
|
||||
},
|
||||
"extends": ["prettier"],
|
||||
"plugins": ["prettier"],
|
||||
"rules": {
|
||||
"prettier/prettier": ["error"]
|
||||
}
|
||||
}
|
4
.prettierrc
Normal file
4
.prettierrc
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
"build:watch": "yarn build --watch",
|
||||
"minify": "minify ./dist/mermaid.js > ./dist/mermaid.min.js",
|
||||
"release": "yarn build -p --config webpack.config.prod.babel.js",
|
||||
"lint": "standard",
|
||||
"lint": "eslint src",
|
||||
"e2e:depr": "yarn lint && jest e2e --config e2e/jest.config.js",
|
||||
"cypress": "percy exec -- cypress run",
|
||||
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
||||
@ -51,11 +51,15 @@
|
||||
"dagre-d3-renderer": "^0.5.8",
|
||||
"dagre-layout": "^0.8.8",
|
||||
"documentation": "^12.0.1",
|
||||
"eslint": "^6.3.0",
|
||||
"eslint-config-prettier": "^6.3.0",
|
||||
"eslint-plugin-prettier": "^3.1.0",
|
||||
"graphlibrary": "^2.2.0",
|
||||
"he": "^1.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"minify": "^4.1.1",
|
||||
"moment-mini": "^2.22.1",
|
||||
"prettier": "^1.18.2",
|
||||
"scope-css": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,28 +1,27 @@
|
||||
let config = {
|
||||
}
|
||||
let config = {};
|
||||
|
||||
const setConf = function (cnf) {
|
||||
const setConf = function(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
const lvl1Keys = Object.keys(cnf)
|
||||
const lvl1Keys = Object.keys(cnf);
|
||||
for (let i = 0; i < lvl1Keys.length; i++) {
|
||||
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
|
||||
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]])
|
||||
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]);
|
||||
|
||||
for (let j = 0; j < lvl2Keys.length; j++) {
|
||||
// logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
|
||||
if (typeof config[lvl1Keys[i]] === 'undefined') {
|
||||
config[lvl1Keys[i]] = {}
|
||||
config[lvl1Keys[i]] = {};
|
||||
}
|
||||
// logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
|
||||
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]]
|
||||
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]];
|
||||
}
|
||||
} else {
|
||||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]]
|
||||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const setConfig = conf => {
|
||||
setConf(conf)
|
||||
}
|
||||
export const getConfig = () => config
|
||||
setConf(conf);
|
||||
};
|
||||
export const getConfig = () => config;
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { logger } from '../../logger';
|
||||
|
||||
import { logger } from '../../logger'
|
||||
|
||||
let relations = []
|
||||
let classes = {}
|
||||
let relations = [];
|
||||
let classes = {};
|
||||
|
||||
/**
|
||||
* Function called by parser when a node definition has been found.
|
||||
@ -11,75 +10,75 @@ let classes = {}
|
||||
* @param type
|
||||
* @param style
|
||||
*/
|
||||
export const addClass = function (id) {
|
||||
export const addClass = function(id) {
|
||||
if (typeof classes[id] === 'undefined') {
|
||||
classes[id] = {
|
||||
id: id,
|
||||
methods: [],
|
||||
members: []
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const clear = function () {
|
||||
relations = []
|
||||
classes = {}
|
||||
}
|
||||
export const clear = function() {
|
||||
relations = [];
|
||||
classes = {};
|
||||
};
|
||||
|
||||
export const getClass = function (id) {
|
||||
return classes[id]
|
||||
}
|
||||
export const getClasses = function () {
|
||||
return classes
|
||||
}
|
||||
export const getClass = function(id) {
|
||||
return classes[id];
|
||||
};
|
||||
export const getClasses = function() {
|
||||
return classes;
|
||||
};
|
||||
|
||||
export const getRelations = function () {
|
||||
return relations
|
||||
}
|
||||
export const getRelations = function() {
|
||||
return relations;
|
||||
};
|
||||
|
||||
export const addRelation = function (relation) {
|
||||
logger.debug('Adding relation: ' + JSON.stringify(relation))
|
||||
addClass(relation.id1)
|
||||
addClass(relation.id2)
|
||||
relations.push(relation)
|
||||
}
|
||||
export const addRelation = function(relation) {
|
||||
logger.debug('Adding relation: ' + JSON.stringify(relation));
|
||||
addClass(relation.id1);
|
||||
addClass(relation.id2);
|
||||
relations.push(relation);
|
||||
};
|
||||
|
||||
export const addMember = function (className, member) {
|
||||
const theClass = classes[className]
|
||||
export const addMember = function(className, member) {
|
||||
const theClass = classes[className];
|
||||
if (typeof member === 'string') {
|
||||
if (member.substr(-1) === ')') {
|
||||
theClass.methods.push(member)
|
||||
theClass.methods.push(member);
|
||||
} else {
|
||||
theClass.members.push(member)
|
||||
theClass.members.push(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const addMembers = function (className, MembersArr) {
|
||||
export const addMembers = function(className, MembersArr) {
|
||||
if (Array.isArray(MembersArr)) {
|
||||
MembersArr.forEach(member => addMember(className, member))
|
||||
MembersArr.forEach(member => addMember(className, member));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const cleanupLabel = function (label) {
|
||||
export const cleanupLabel = function(label) {
|
||||
if (label.substring(0, 1) === ':') {
|
||||
return label.substr(2).trim()
|
||||
return label.substr(2).trim();
|
||||
} else {
|
||||
return label.trim()
|
||||
return label.trim();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const lineType = {
|
||||
LINE: 0,
|
||||
DOTTED_LINE: 1
|
||||
}
|
||||
};
|
||||
|
||||
export const relationType = {
|
||||
AGGREGATION: 0,
|
||||
EXTENSION: 1,
|
||||
COMPOSITION: 2,
|
||||
DEPENDENCY: 3
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
addClass,
|
||||
@ -93,4 +92,4 @@ export default {
|
||||
cleanupLabel,
|
||||
lineType,
|
||||
relationType
|
||||
}
|
||||
};
|
||||
|
@ -1,208 +1,211 @@
|
||||
/* eslint-env jasmine */
|
||||
import { parser } from './parser/classDiagram'
|
||||
import classDb from './classDb'
|
||||
import { parser } from './parser/classDiagram';
|
||||
import classDb from './classDb';
|
||||
|
||||
describe('class diagram, ', function () {
|
||||
describe('when parsing an info graph it', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb
|
||||
})
|
||||
describe('class diagram, ', function() {
|
||||
describe('when parsing an info graph it', function() {
|
||||
beforeEach(function() {
|
||||
parser.yy = classDb;
|
||||
});
|
||||
|
||||
it('should handle relation definitions', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 <|-- Class02\n' +
|
||||
'Class03 *-- Class04\n' +
|
||||
'Class05 o-- Class06\n' +
|
||||
'Class07 .. Class08\n' +
|
||||
'Class09 -- Class1'
|
||||
it('should handle relation definitions', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class01 <|-- Class02\n' +
|
||||
'Class03 *-- Class04\n' +
|
||||
'Class05 o-- Class06\n' +
|
||||
'Class07 .. Class08\n' +
|
||||
'Class09 -- Class1';
|
||||
|
||||
parser.parse(str)
|
||||
})
|
||||
it('should handle relation definition of different types and directions', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class11 <|.. Class12\n' +
|
||||
'Class13 --> Class14\n' +
|
||||
'Class15 ..> Class16\n' +
|
||||
'Class17 ..|> Class18\n' +
|
||||
'Class19 <--* Class20'
|
||||
parser.parse(str);
|
||||
});
|
||||
it('should handle relation definition of different types and directions', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class11 <|.. Class12\n' +
|
||||
'Class13 --> Class14\n' +
|
||||
'Class15 ..> Class16\n' +
|
||||
'Class17 ..|> Class18\n' +
|
||||
'Class19 <--* Class20';
|
||||
|
||||
parser.parse(str)
|
||||
})
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle cardinality and labels', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 "1" *-- "many" Class02 : contains\n' +
|
||||
'Class03 o-- Class04 : aggregation\n' +
|
||||
'Class05 --> "1" Class06'
|
||||
it('should handle cardinality and labels', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class01 "1" *-- "many" Class02 : contains\n' +
|
||||
'Class03 o-- Class04 : aggregation\n' +
|
||||
'Class05 --> "1" Class06';
|
||||
|
||||
parser.parse(str)
|
||||
})
|
||||
it('should handle class definitions', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'class Car\n' +
|
||||
'Driver -- Car : drives >\n' +
|
||||
'Car *-- Wheel : have 4 >\n' +
|
||||
'Car -- Person : < owns'
|
||||
parser.parse(str);
|
||||
});
|
||||
it('should handle class definitions', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Car\n' +
|
||||
'Driver -- Car : drives >\n' +
|
||||
'Car *-- Wheel : have 4 >\n' +
|
||||
'Car -- Person : < owns';
|
||||
|
||||
parser.parse(str)
|
||||
})
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle method statements', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Object <|-- ArrayList\n' +
|
||||
'Object : equals()\n' +
|
||||
'ArrayList : Object[] elementData\n' +
|
||||
'ArrayList : size()'
|
||||
it('should handle method statements', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Object <|-- ArrayList\n' +
|
||||
'Object : equals()\n' +
|
||||
'ArrayList : Object[] elementData\n' +
|
||||
'ArrayList : size()';
|
||||
|
||||
parser.parse(str)
|
||||
})
|
||||
it('should handle parsing of method statements grouped by brackets', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'class Dummy {\n' +
|
||||
'String data\n' +
|
||||
' void methods()\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Flight {\n' +
|
||||
' flightNumber : Integer\n' +
|
||||
' departureTime : Date\n' +
|
||||
'}'
|
||||
parser.parse(str);
|
||||
});
|
||||
it('should handle parsing of method statements grouped by brackets', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy {\n' +
|
||||
'String data\n' +
|
||||
' void methods()\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Flight {\n' +
|
||||
' flightNumber : Integer\n' +
|
||||
' departureTime : Date\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str)
|
||||
})
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle parsing of separators', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'class Foo1 {\n' +
|
||||
' You can use\n' +
|
||||
' several lines\n' +
|
||||
'..\n' +
|
||||
'as you want\n' +
|
||||
'and group\n' +
|
||||
'==\n' +
|
||||
'things together.\n' +
|
||||
'__\n' +
|
||||
'You can have as many groups\n' +
|
||||
'as you want\n' +
|
||||
'--\n' +
|
||||
'End of class\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class User {\n' +
|
||||
'.. Simple Getter ..\n' +
|
||||
'+ getName()\n' +
|
||||
'+ getAddress()\n' +
|
||||
'.. Some setter ..\n' +
|
||||
'+ setName()\n' +
|
||||
'__ private data __\n' +
|
||||
'int age\n' +
|
||||
'-- encrypted --\n' +
|
||||
'String password\n' +
|
||||
'}'
|
||||
it('should handle parsing of separators', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Foo1 {\n' +
|
||||
' You can use\n' +
|
||||
' several lines\n' +
|
||||
'..\n' +
|
||||
'as you want\n' +
|
||||
'and group\n' +
|
||||
'==\n' +
|
||||
'things together.\n' +
|
||||
'__\n' +
|
||||
'You can have as many groups\n' +
|
||||
'as you want\n' +
|
||||
'--\n' +
|
||||
'End of class\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class User {\n' +
|
||||
'.. Simple Getter ..\n' +
|
||||
'+ getName()\n' +
|
||||
'+ getAddress()\n' +
|
||||
'.. Some setter ..\n' +
|
||||
'+ setName()\n' +
|
||||
'__ private data __\n' +
|
||||
'int age\n' +
|
||||
'-- encrypted --\n' +
|
||||
'String password\n' +
|
||||
'}';
|
||||
|
||||
parser.parse(str)
|
||||
})
|
||||
})
|
||||
parser.parse(str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when fetching data from an classDiagram graph it', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb
|
||||
parser.yy.clear()
|
||||
})
|
||||
it('should handle relation definitions EXTENSION', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 <|-- Class02'
|
||||
describe('when fetching data from an classDiagram graph it', function() {
|
||||
beforeEach(function() {
|
||||
parser.yy = classDb;
|
||||
parser.yy.clear();
|
||||
});
|
||||
it('should handle relation definitions EXTENSION', function() {
|
||||
const str = 'classDiagram\n' + 'Class01 <|-- Class02';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations()
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01')
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02')
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION)
|
||||
expect(relations[0].relation.type2).toBe('none')
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
|
||||
})
|
||||
it('should handle relation definitions AGGREGATION and dotted line', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 o.. Class02'
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION);
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
it('should handle relation definitions AGGREGATION and dotted line', function() {
|
||||
const str = 'classDiagram\n' + 'Class01 o.. Class02';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations()
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01')
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02')
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION)
|
||||
expect(relations[0].relation.type2).toBe('none')
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE)
|
||||
})
|
||||
it('should handle relation definitions COMPOSITION on both sides', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 *--* Class02'
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.AGGREGATION);
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.DOTTED_LINE);
|
||||
});
|
||||
it('should handle relation definitions COMPOSITION on both sides', function() {
|
||||
const str = 'classDiagram\n' + 'Class01 *--* Class02';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations()
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01')
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02')
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION)
|
||||
expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION)
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
|
||||
})
|
||||
it('should handle relation definitions no types', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 -- Class02'
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.COMPOSITION);
|
||||
expect(relations[0].relation.type2).toBe(classDb.relationType.COMPOSITION);
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
it('should handle relation definitions no types', function() {
|
||||
const str = 'classDiagram\n' + 'Class01 -- Class02';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations()
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01')
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02')
|
||||
expect(relations[0].relation.type1).toBe('none')
|
||||
expect(relations[0].relation.type2).toBe('none')
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
|
||||
})
|
||||
it('should handle relation definitions with type only on right side', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 --|> Class02'
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe('none');
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
it('should handle relation definitions with type only on right side', function() {
|
||||
const str = 'classDiagram\n' + 'Class01 --|> Class02';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations()
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01')
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02')
|
||||
expect(relations[0].relation.type1).toBe('none')
|
||||
expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION)
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
|
||||
})
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe('none');
|
||||
expect(relations[0].relation.type2).toBe(classDb.relationType.EXTENSION);
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
|
||||
it('should handle multiple classes and relation definitions', function () {
|
||||
const str = 'classDiagram\n' +
|
||||
'Class01 <|-- Class02\n' +
|
||||
'Class03 *-- Class04\n' +
|
||||
'Class05 o-- Class06\n' +
|
||||
'Class07 .. Class08\n' +
|
||||
'Class09 -- Class10'
|
||||
it('should handle multiple classes and relation definitions', function() {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'Class01 <|-- Class02\n' +
|
||||
'Class03 *-- Class04\n' +
|
||||
'Class05 o-- Class06\n' +
|
||||
'Class07 .. Class08\n' +
|
||||
'Class09 -- Class10';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations()
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01')
|
||||
expect(parser.yy.getClass('Class10').id).toBe('Class10')
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class10').id).toBe('Class10');
|
||||
|
||||
expect(relations.length).toBe(5)
|
||||
expect(relations.length).toBe(5);
|
||||
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION)
|
||||
expect(relations[0].relation.type2).toBe('none')
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE)
|
||||
expect(relations[3].relation.type1).toBe('none')
|
||||
expect(relations[3].relation.type2).toBe('none')
|
||||
expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE)
|
||||
})
|
||||
})
|
||||
})
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION);
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
expect(relations[3].relation.type1).toBe('none');
|
||||
expect(relations[3].relation.type2).toBe('none');
|
||||
expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,38 +1,38 @@
|
||||
import * as d3 from 'd3'
|
||||
import dagre from 'dagre-layout'
|
||||
import graphlib from 'graphlibrary'
|
||||
import { logger } from '../../logger'
|
||||
import classDb from './classDb'
|
||||
import { parser } from './parser/classDiagram'
|
||||
import * as d3 from 'd3';
|
||||
import dagre from 'dagre-layout';
|
||||
import graphlib from 'graphlibrary';
|
||||
import { logger } from '../../logger';
|
||||
import classDb from './classDb';
|
||||
import { parser } from './parser/classDiagram';
|
||||
|
||||
parser.yy = classDb
|
||||
parser.yy = classDb;
|
||||
|
||||
const idCache = {}
|
||||
const idCache = {};
|
||||
|
||||
let classCnt = 0
|
||||
let classCnt = 0;
|
||||
const conf = {
|
||||
dividerMargin: 10,
|
||||
padding: 5,
|
||||
textHeight: 10
|
||||
}
|
||||
};
|
||||
|
||||
// Todo optimize
|
||||
const getGraphId = function (label) {
|
||||
const keys = Object.keys(idCache)
|
||||
const getGraphId = function(label) {
|
||||
const keys = Object.keys(idCache);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (idCache[keys[i]].label === label) {
|
||||
return keys[i]
|
||||
return keys[i];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*/
|
||||
const insertMarkers = function (elem) {
|
||||
const insertMarkers = function(elem) {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
@ -44,7 +44,7 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 1,7 L18,13 V 1 Z')
|
||||
.attr('d', 'M 1,7 L18,13 V 1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
@ -56,7 +56,7 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 1,1 V 13 L18,7 Z') // this is actual shape for arrowhead
|
||||
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
@ -69,7 +69,7 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
@ -81,7 +81,7 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
@ -94,7 +94,7 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
@ -106,7 +106,7 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
@ -119,7 +119,7 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z')
|
||||
.attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
@ -131,96 +131,86 @@ const insertMarkers = function (elem) {
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z')
|
||||
}
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
let edgeCount = 0
|
||||
let total = 0
|
||||
const drawEdge = function (elem, path, relation) {
|
||||
const getRelationType = function (type) {
|
||||
let edgeCount = 0;
|
||||
let total = 0;
|
||||
const drawEdge = function(elem, path, relation) {
|
||||
const getRelationType = function(type) {
|
||||
switch (type) {
|
||||
case classDb.relationType.AGGREGATION:
|
||||
return 'aggregation'
|
||||
return 'aggregation';
|
||||
case classDb.relationType.EXTENSION:
|
||||
return 'extension'
|
||||
return 'extension';
|
||||
case classDb.relationType.COMPOSITION:
|
||||
return 'composition'
|
||||
return 'composition';
|
||||
case classDb.relationType.DEPENDENCY:
|
||||
return 'dependency'
|
||||
return 'dependency';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
path.points = path.points.filter(p => !Number.isNaN(p.y))
|
||||
path.points = path.points.filter(p => !Number.isNaN(p.y));
|
||||
|
||||
// The data for our line
|
||||
const lineData = path.points
|
||||
const lineData = path.points;
|
||||
|
||||
// This is the accessor function we talked about above
|
||||
const lineFunction = d3
|
||||
.line()
|
||||
.x(function (d) {
|
||||
return d.x
|
||||
.x(function(d) {
|
||||
return d.x;
|
||||
})
|
||||
.y(function (d) {
|
||||
return d.y
|
||||
.y(function(d) {
|
||||
return d.y;
|
||||
})
|
||||
.curve(d3.curveBasis)
|
||||
.curve(d3.curveBasis);
|
||||
|
||||
const svgPath = elem
|
||||
.append('path')
|
||||
.attr('d', lineFunction(lineData))
|
||||
.attr('id', 'edge' + edgeCount)
|
||||
.attr('class', 'relation')
|
||||
let url = ''
|
||||
.attr('class', 'relation');
|
||||
let url = '';
|
||||
if (conf.arrowMarkerAbsolute) {
|
||||
url =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
window.location.search
|
||||
url = url.replace(/\(/g, '\\(')
|
||||
url = url.replace(/\)/g, '\\)')
|
||||
window.location.search;
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
if (relation.relation.type1 !== 'none') {
|
||||
svgPath.attr(
|
||||
'marker-start',
|
||||
'url(' +
|
||||
url +
|
||||
'#' +
|
||||
getRelationType(relation.relation.type1) +
|
||||
'Start' +
|
||||
')'
|
||||
)
|
||||
'url(' + url + '#' + getRelationType(relation.relation.type1) + 'Start' + ')'
|
||||
);
|
||||
}
|
||||
if (relation.relation.type2 !== 'none') {
|
||||
svgPath.attr(
|
||||
'marker-end',
|
||||
'url(' +
|
||||
url +
|
||||
'#' +
|
||||
getRelationType(relation.relation.type2) +
|
||||
'End' +
|
||||
')'
|
||||
)
|
||||
'url(' + url + '#' + getRelationType(relation.relation.type2) + 'End' + ')'
|
||||
);
|
||||
}
|
||||
|
||||
let x, y
|
||||
const l = path.points.length
|
||||
let x, y;
|
||||
const l = path.points.length;
|
||||
if (l % 2 !== 0 && l > 1) {
|
||||
const p1 = path.points[Math.floor(l / 2)]
|
||||
const p2 = path.points[Math.ceil(l / 2)]
|
||||
x = (p1.x + p2.x) / 2
|
||||
y = (p1.y + p2.y) / 2
|
||||
const p1 = path.points[Math.floor(l / 2)];
|
||||
const p2 = path.points[Math.ceil(l / 2)];
|
||||
x = (p1.x + p2.x) / 2;
|
||||
y = (p1.y + p2.y) / 2;
|
||||
} else {
|
||||
const p = path.points[Math.floor(l / 2)]
|
||||
x = p.x
|
||||
y = p.y
|
||||
const p = path.points[Math.floor(l / 2)];
|
||||
x = p.x;
|
||||
y = p.y;
|
||||
}
|
||||
|
||||
if (typeof relation.title !== 'undefined') {
|
||||
const g = elem.append('g').attr('class', 'classLabel')
|
||||
const g = elem.append('g').attr('class', 'classLabel');
|
||||
const label = g
|
||||
.append('text')
|
||||
.attr('class', 'label')
|
||||
@ -228,189 +218,177 @@ const drawEdge = function (elem, path, relation) {
|
||||
.attr('y', y)
|
||||
.attr('fill', 'red')
|
||||
.attr('text-anchor', 'middle')
|
||||
.text(relation.title)
|
||||
.text(relation.title);
|
||||
|
||||
window.label = label
|
||||
const bounds = label.node().getBBox()
|
||||
window.label = label;
|
||||
const bounds = label.node().getBBox();
|
||||
|
||||
g.insert('rect', ':first-child')
|
||||
.attr('class', 'box')
|
||||
.attr('x', bounds.x - conf.padding / 2)
|
||||
.attr('y', bounds.y - conf.padding / 2)
|
||||
.attr('width', bounds.width + conf.padding)
|
||||
.attr('height', bounds.height + conf.padding)
|
||||
.attr('height', bounds.height + conf.padding);
|
||||
}
|
||||
|
||||
edgeCount++
|
||||
}
|
||||
edgeCount++;
|
||||
};
|
||||
|
||||
const drawClass = function (elem, classDef) {
|
||||
logger.info('Rendering class ' + classDef)
|
||||
const drawClass = function(elem, classDef) {
|
||||
logger.info('Rendering class ' + classDef);
|
||||
|
||||
const addTspan = function (textEl, txt, isFirst) {
|
||||
const addTspan = function(textEl, txt, isFirst) {
|
||||
const tSpan = textEl
|
||||
.append('tspan')
|
||||
.attr('x', conf.padding)
|
||||
.text(txt)
|
||||
.text(txt);
|
||||
if (!isFirst) {
|
||||
tSpan.attr('dy', conf.textHeight)
|
||||
tSpan.attr('dy', conf.textHeight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const id = 'classId' + (classCnt % total)
|
||||
const id = 'classId' + (classCnt % total);
|
||||
const classInfo = {
|
||||
id: id,
|
||||
label: classDef.id,
|
||||
width: 0,
|
||||
height: 0
|
||||
}
|
||||
};
|
||||
|
||||
const g = elem
|
||||
.append('g')
|
||||
.attr('id', id)
|
||||
.attr('class', 'classGroup')
|
||||
.attr('class', 'classGroup');
|
||||
const title = g
|
||||
.append('text')
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', conf.textHeight + conf.padding)
|
||||
.text(classDef.id)
|
||||
.text(classDef.id);
|
||||
|
||||
const titleHeight = title.node().getBBox().height
|
||||
const titleHeight = title.node().getBBox().height;
|
||||
|
||||
const membersLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
|
||||
|
||||
const members = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText')
|
||||
.attr('class', 'classText');
|
||||
|
||||
let isFirst = true
|
||||
classDef.members.forEach(function (member) {
|
||||
addTspan(members, member, isFirst)
|
||||
isFirst = false
|
||||
})
|
||||
let isFirst = true;
|
||||
classDef.members.forEach(function(member) {
|
||||
addTspan(members, member, isFirst);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
const membersBox = members.node().getBBox()
|
||||
const membersBox = members.node().getBBox();
|
||||
|
||||
const methodsLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr(
|
||||
'y1',
|
||||
conf.padding + titleHeight + conf.dividerMargin + membersBox.height
|
||||
)
|
||||
.attr(
|
||||
'y2',
|
||||
conf.padding + titleHeight + conf.dividerMargin + membersBox.height
|
||||
)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
|
||||
|
||||
const methods = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr(
|
||||
'y',
|
||||
titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight
|
||||
)
|
||||
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText')
|
||||
.attr('class', 'classText');
|
||||
|
||||
isFirst = true
|
||||
isFirst = true;
|
||||
|
||||
classDef.methods.forEach(function (method) {
|
||||
addTspan(methods, method, isFirst)
|
||||
isFirst = false
|
||||
})
|
||||
classDef.methods.forEach(function(method) {
|
||||
addTspan(methods, method, isFirst);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
const classBox = g.node().getBBox()
|
||||
const classBox = g.node().getBBox();
|
||||
g.insert('rect', ':first-child')
|
||||
.attr('x', 0)
|
||||
.attr('y', 0)
|
||||
.attr('width', classBox.width + 2 * conf.padding)
|
||||
.attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin)
|
||||
.attr('height', classBox.height + conf.padding + 0.5 * conf.dividerMargin);
|
||||
|
||||
membersLine.attr('x2', classBox.width + 2 * conf.padding)
|
||||
methodsLine.attr('x2', classBox.width + 2 * conf.padding)
|
||||
membersLine.attr('x2', classBox.width + 2 * conf.padding);
|
||||
methodsLine.attr('x2', classBox.width + 2 * conf.padding);
|
||||
|
||||
classInfo.width = classBox.width + 2 * conf.padding
|
||||
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin
|
||||
classInfo.width = classBox.width + 2 * conf.padding;
|
||||
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
|
||||
|
||||
idCache[id] = classInfo
|
||||
classCnt++
|
||||
return classInfo
|
||||
}
|
||||
idCache[id] = classInfo;
|
||||
classCnt++;
|
||||
return classInfo;
|
||||
};
|
||||
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf)
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
keys.forEach(function (key) {
|
||||
conf[key] = cnf[key]
|
||||
})
|
||||
}
|
||||
keys.forEach(function(key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
export const draw = function (text, id) {
|
||||
parser.yy.clear()
|
||||
parser.parse(text)
|
||||
export const draw = function(text, id) {
|
||||
parser.yy.clear();
|
||||
parser.parse(text);
|
||||
|
||||
logger.info('Rendering diagram ' + text)
|
||||
logger.info('Rendering diagram ' + text);
|
||||
|
||||
/// / Fetch the default direction, use TD if none was found
|
||||
const diagram = d3.select(`[id='${id}']`)
|
||||
insertMarkers(diagram)
|
||||
const diagram = d3.select(`[id='${id}']`);
|
||||
insertMarkers(diagram);
|
||||
|
||||
// Layout graph, Create a new directed graph
|
||||
const g = new graphlib.Graph({
|
||||
multigraph: true
|
||||
})
|
||||
});
|
||||
|
||||
// Set an object for the graph label
|
||||
g.setGraph({
|
||||
isMultiGraph: true
|
||||
})
|
||||
});
|
||||
|
||||
// Default to assigning a new object as a label for each new edge.
|
||||
g.setDefaultEdgeLabel(function () {
|
||||
return {}
|
||||
})
|
||||
g.setDefaultEdgeLabel(function() {
|
||||
return {};
|
||||
});
|
||||
|
||||
const classes = classDb.getClasses()
|
||||
const keys = Object.keys(classes)
|
||||
total = keys.length
|
||||
const classes = classDb.getClasses();
|
||||
const keys = Object.keys(classes);
|
||||
total = keys.length;
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const classDef = classes[keys[i]]
|
||||
const node = drawClass(diagram, classDef)
|
||||
const classDef = classes[keys[i]];
|
||||
const node = drawClass(diagram, classDef);
|
||||
// Add nodes to the graph. The first argument is the node id. The second is
|
||||
// metadata about the node. In this case we're going to add labels to each of
|
||||
// our nodes.
|
||||
g.setNode(node.id, node)
|
||||
logger.info('Org height: ' + node.height)
|
||||
g.setNode(node.id, node);
|
||||
logger.info('Org height: ' + node.height);
|
||||
}
|
||||
|
||||
const relations = classDb.getRelations()
|
||||
relations.forEach(function (relation) {
|
||||
const relations = classDb.getRelations();
|
||||
relations.forEach(function(relation) {
|
||||
logger.info(
|
||||
'tjoho' +
|
||||
getGraphId(relation.id1) +
|
||||
getGraphId(relation.id2) +
|
||||
JSON.stringify(relation)
|
||||
)
|
||||
'tjoho' + getGraphId(relation.id1) + getGraphId(relation.id2) + JSON.stringify(relation)
|
||||
);
|
||||
g.setEdge(getGraphId(relation.id1), getGraphId(relation.id2), {
|
||||
relation: relation
|
||||
})
|
||||
})
|
||||
dagre.layout(g)
|
||||
g.nodes().forEach(function (v) {
|
||||
});
|
||||
});
|
||||
dagre.layout(g);
|
||||
g.nodes().forEach(function(v) {
|
||||
if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') {
|
||||
logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)))
|
||||
logger.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)));
|
||||
d3.select('#' + v).attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
@ -418,27 +396,22 @@ export const draw = function (text, id) {
|
||||
',' +
|
||||
(g.node(v).y - g.node(v).height / 2) +
|
||||
' )'
|
||||
)
|
||||
);
|
||||
}
|
||||
})
|
||||
g.edges().forEach(function (e) {
|
||||
});
|
||||
g.edges().forEach(function(e) {
|
||||
if (typeof e !== 'undefined' && typeof g.edge(e) !== 'undefined') {
|
||||
logger.debug(
|
||||
'Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e))
|
||||
)
|
||||
drawEdge(diagram, g.edge(e), g.edge(e).relation)
|
||||
logger.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e)));
|
||||
drawEdge(diagram, g.edge(e), g.edge(e).relation);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
diagram.attr('height', '100%')
|
||||
diagram.attr('width', '100%')
|
||||
diagram.attr(
|
||||
'viewBox',
|
||||
'0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20)
|
||||
)
|
||||
}
|
||||
diagram.attr('height', '100%');
|
||||
diagram.attr('width', '100%');
|
||||
diagram.attr('viewBox', '0 0 ' + (g.graph().width + 20) + ' ' + (g.graph().height + 20));
|
||||
};
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw
|
||||
}
|
||||
};
|
||||
|
@ -1,33 +1,33 @@
|
||||
import * as d3 from 'd3'
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url'
|
||||
import { logger } from '../../logger'
|
||||
import utils from '../../utils'
|
||||
import { getConfig } from '../../config'
|
||||
import * as d3 from 'd3';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import { logger } from '../../logger';
|
||||
import utils from '../../utils';
|
||||
import { getConfig } from '../../config';
|
||||
|
||||
const config = getConfig()
|
||||
let vertices = {}
|
||||
let edges = []
|
||||
let classes = []
|
||||
let subGraphs = []
|
||||
let subGraphLookup = {}
|
||||
let tooltips = {}
|
||||
let subCount = 0
|
||||
let direction
|
||||
const config = getConfig();
|
||||
let vertices = {};
|
||||
let edges = [];
|
||||
let classes = [];
|
||||
let subGraphs = [];
|
||||
let subGraphLookup = {};
|
||||
let tooltips = {};
|
||||
let subCount = 0;
|
||||
let direction;
|
||||
// Functions to be run after graph rendering
|
||||
let funs = []
|
||||
let funs = [];
|
||||
|
||||
const sanitize = text => {
|
||||
let txt = text
|
||||
let txt = text;
|
||||
if (config.securityLevel !== 'loose') {
|
||||
txt = txt.replace(/<br>/g, '#br#')
|
||||
txt = txt.replace(/<br\S*?\/>/g, '#br#')
|
||||
txt = txt.replace(/</g, '<').replace(/>/g, '>')
|
||||
txt = txt.replace(/=/g, '=')
|
||||
txt = txt.replace(/#br#/g, '<br/>')
|
||||
txt = txt.replace(/<br>/g, '#br#');
|
||||
txt = txt.replace(/<br\S*?\/>/g, '#br#');
|
||||
txt = txt.replace(/</g, '<').replace(/>/g, '>');
|
||||
txt = txt.replace(/=/g, '=');
|
||||
txt = txt.replace(/#br#/g, '<br/>');
|
||||
}
|
||||
|
||||
return txt
|
||||
}
|
||||
return txt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a node definition has been found
|
||||
@ -37,53 +37,53 @@ const sanitize = text => {
|
||||
* @param style
|
||||
* @param classes
|
||||
*/
|
||||
export const addVertex = function (_id, text, type, style, classes) {
|
||||
let txt
|
||||
let id = _id
|
||||
export const addVertex = function(_id, text, type, style, classes) {
|
||||
let txt;
|
||||
let id = _id;
|
||||
if (typeof id === 'undefined') {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (id.trim().length === 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (id[0].match(/\d/)) id = 's' + id
|
||||
if (id[0].match(/\d/)) id = 's' + id;
|
||||
|
||||
if (typeof vertices[id] === 'undefined') {
|
||||
vertices[id] = { id: id, styles: [], classes: [] }
|
||||
vertices[id] = { id: id, styles: [], classes: [] };
|
||||
}
|
||||
if (typeof text !== 'undefined') {
|
||||
txt = sanitize(text.trim())
|
||||
txt = sanitize(text.trim());
|
||||
|
||||
// strip quotes if string starts and exnds with a quote
|
||||
if (txt[0] === '"' && txt[txt.length - 1] === '"') {
|
||||
txt = txt.substring(1, txt.length - 1)
|
||||
txt = txt.substring(1, txt.length - 1);
|
||||
}
|
||||
|
||||
vertices[id].text = txt
|
||||
vertices[id].text = txt;
|
||||
} else {
|
||||
if (!vertices[id].text) {
|
||||
vertices[id].text = _id
|
||||
vertices[id].text = _id;
|
||||
}
|
||||
}
|
||||
if (typeof type !== 'undefined') {
|
||||
vertices[id].type = type
|
||||
vertices[id].type = type;
|
||||
}
|
||||
if (typeof style !== 'undefined') {
|
||||
if (style !== null) {
|
||||
style.forEach(function (s) {
|
||||
vertices[id].styles.push(s)
|
||||
})
|
||||
style.forEach(function(s) {
|
||||
vertices[id].styles.push(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (typeof classes !== 'undefined') {
|
||||
if (classes !== null) {
|
||||
classes.forEach(function (s) {
|
||||
vertices[id].classes.push(s)
|
||||
})
|
||||
classes.forEach(function(s) {
|
||||
vertices[id].classes.push(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a link/edge definition has been found
|
||||
@ -92,134 +92,138 @@ export const addVertex = function (_id, text, type, style, classes) {
|
||||
* @param type
|
||||
* @param linktext
|
||||
*/
|
||||
export const addLink = function (_start, _end, type, linktext) {
|
||||
let start = _start
|
||||
let end = _end
|
||||
if (start[0].match(/\d/)) start = 's' + start
|
||||
if (end[0].match(/\d/)) end = 's' + end
|
||||
logger.info('Got edge...', start, end)
|
||||
export const addLink = function(_start, _end, type, linktext) {
|
||||
let start = _start;
|
||||
let end = _end;
|
||||
if (start[0].match(/\d/)) start = 's' + start;
|
||||
if (end[0].match(/\d/)) end = 's' + end;
|
||||
logger.info('Got edge...', start, end);
|
||||
|
||||
const edge = { start: start, end: end, type: undefined, text: '' }
|
||||
linktext = type.text
|
||||
const edge = { start: start, end: end, type: undefined, text: '' };
|
||||
linktext = type.text;
|
||||
|
||||
if (typeof linktext !== 'undefined') {
|
||||
edge.text = sanitize(linktext.trim())
|
||||
edge.text = sanitize(linktext.trim());
|
||||
|
||||
// strip quotes if string starts and exnds with a quote
|
||||
if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') {
|
||||
edge.text = edge.text.substring(1, edge.text.length - 1)
|
||||
edge.text = edge.text.substring(1, edge.text.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof type !== 'undefined') {
|
||||
edge.type = type.type
|
||||
edge.stroke = type.stroke
|
||||
edge.type = type.type;
|
||||
edge.stroke = type.stroke;
|
||||
}
|
||||
edges.push(edge)
|
||||
}
|
||||
edges.push(edge);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a link's line interpolation algorithm
|
||||
* @param pos
|
||||
* @param interpolate
|
||||
*/
|
||||
export const updateLinkInterpolate = function (positions, interp) {
|
||||
positions.forEach(function (pos) {
|
||||
export const updateLinkInterpolate = function(positions, interp) {
|
||||
positions.forEach(function(pos) {
|
||||
if (pos === 'default') {
|
||||
edges.defaultInterpolate = interp
|
||||
edges.defaultInterpolate = interp;
|
||||
} else {
|
||||
edges[pos].interpolate = interp
|
||||
edges[pos].interpolate = interp;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates a link with a style
|
||||
* @param pos
|
||||
* @param style
|
||||
*/
|
||||
export const updateLink = function (positions, style) {
|
||||
positions.forEach(function (pos) {
|
||||
export const updateLink = function(positions, style) {
|
||||
positions.forEach(function(pos) {
|
||||
if (pos === 'default') {
|
||||
edges.defaultStyle = style
|
||||
edges.defaultStyle = style;
|
||||
} else {
|
||||
if (utils.isSubstringInArray('fill', style) === -1) {
|
||||
style.push('fill:none')
|
||||
style.push('fill:none');
|
||||
}
|
||||
edges[pos].style = style
|
||||
edges[pos].style = style;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const addClass = function (id, style) {
|
||||
export const addClass = function(id, style) {
|
||||
if (typeof classes[id] === 'undefined') {
|
||||
classes[id] = { id: id, styles: [] }
|
||||
classes[id] = { id: id, styles: [] };
|
||||
}
|
||||
|
||||
if (typeof style !== 'undefined') {
|
||||
if (style !== null) {
|
||||
style.forEach(function (s) {
|
||||
classes[id].styles.push(s)
|
||||
})
|
||||
style.forEach(function(s) {
|
||||
classes[id].styles.push(s);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a graph definition is found, stores the direction of the chart.
|
||||
* @param dir
|
||||
*/
|
||||
export const setDirection = function (dir) {
|
||||
direction = dir
|
||||
}
|
||||
export const setDirection = function(dir) {
|
||||
direction = dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a special node is found, e.g. a clickable element.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param className Class to add
|
||||
*/
|
||||
export const setClass = function (ids, className) {
|
||||
ids.split(',').forEach(function (_id) {
|
||||
let id = _id
|
||||
if (_id[0].match(/\d/)) id = 's' + id
|
||||
export const setClass = function(ids, className) {
|
||||
ids.split(',').forEach(function(_id) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = 's' + id;
|
||||
if (typeof vertices[id] !== 'undefined') {
|
||||
vertices[id].classes.push(className)
|
||||
vertices[id].classes.push(className);
|
||||
}
|
||||
|
||||
if (typeof subGraphLookup[id] !== 'undefined') {
|
||||
subGraphLookup[id].classes.push(className)
|
||||
subGraphLookup[id].classes.push(className);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const setTooltip = function (ids, tooltip) {
|
||||
ids.split(',').forEach(function (id) {
|
||||
const setTooltip = function(ids, tooltip) {
|
||||
ids.split(',').forEach(function(id) {
|
||||
if (typeof tooltip !== 'undefined') {
|
||||
tooltips[id] = sanitize(tooltip)
|
||||
tooltips[id] = sanitize(tooltip);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const setClickFun = function (_id, functionName) {
|
||||
let id = _id
|
||||
if (_id[0].match(/\d/)) id = 's' + id
|
||||
const setClickFun = function(_id, functionName) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = 's' + id;
|
||||
if (config.securityLevel !== 'loose') {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (typeof functionName === 'undefined') {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (typeof vertices[id] !== 'undefined') {
|
||||
funs.push(function (element) {
|
||||
const elem = document.querySelector(`[id="${id}"]`)
|
||||
funs.push(function(element) {
|
||||
const elem = document.querySelector(`[id="${id}"]`);
|
||||
if (elem !== null) {
|
||||
elem.addEventListener('click', function () {
|
||||
window[functionName](id)
|
||||
}, false)
|
||||
elem.addEventListener(
|
||||
'click',
|
||||
function() {
|
||||
window[functionName](id);
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a link is found. Adds the URL to the vertex data.
|
||||
@ -227,24 +231,24 @@ const setClickFun = function (_id, functionName) {
|
||||
* @param linkStr URL to create a link for
|
||||
* @param tooltip Tooltip for the clickable element
|
||||
*/
|
||||
export const setLink = function (ids, linkStr, tooltip) {
|
||||
ids.split(',').forEach(function (_id) {
|
||||
let id = _id
|
||||
if (_id[0].match(/\d/)) id = 's' + id
|
||||
export const setLink = function(ids, linkStr, tooltip) {
|
||||
ids.split(',').forEach(function(_id) {
|
||||
let id = _id;
|
||||
if (_id[0].match(/\d/)) id = 's' + id;
|
||||
if (typeof vertices[id] !== 'undefined') {
|
||||
if (config.securityLevel !== 'loose') {
|
||||
vertices[id].link = sanitizeUrl(linkStr) // .replace(/javascript:.*/g, '')
|
||||
vertices[id].link = sanitizeUrl(linkStr); // .replace(/javascript:.*/g, '')
|
||||
} else {
|
||||
vertices[id].link = linkStr
|
||||
vertices[id].link = linkStr;
|
||||
}
|
||||
}
|
||||
})
|
||||
setTooltip(ids, tooltip)
|
||||
setClass(ids, 'clickable')
|
||||
}
|
||||
export const getTooltip = function (id) {
|
||||
return tooltips[id]
|
||||
}
|
||||
});
|
||||
setTooltip(ids, tooltip);
|
||||
setClass(ids, 'clickable');
|
||||
};
|
||||
export const getTooltip = function(id) {
|
||||
return tooltips[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a click definition is found. Registers an event handler.
|
||||
@ -252,209 +256,219 @@ export const getTooltip = function (id) {
|
||||
* @param functionName Function to be called on click
|
||||
* @param tooltip Tooltip for the clickable element
|
||||
*/
|
||||
export const setClickEvent = function (ids, functionName, tooltip) {
|
||||
ids.split(',').forEach(function (id) { setClickFun(id, functionName) })
|
||||
setTooltip(ids, tooltip)
|
||||
setClass(ids, 'clickable')
|
||||
}
|
||||
export const setClickEvent = function(ids, functionName, tooltip) {
|
||||
ids.split(',').forEach(function(id) {
|
||||
setClickFun(id, functionName);
|
||||
});
|
||||
setTooltip(ids, tooltip);
|
||||
setClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
export const bindFunctions = function (element) {
|
||||
funs.forEach(function (fun) {
|
||||
fun(element)
|
||||
})
|
||||
}
|
||||
export const getDirection = function () {
|
||||
return direction
|
||||
}
|
||||
export const bindFunctions = function(element) {
|
||||
funs.forEach(function(fun) {
|
||||
fun(element);
|
||||
});
|
||||
};
|
||||
export const getDirection = function() {
|
||||
return direction;
|
||||
};
|
||||
/**
|
||||
* Retrieval function for fetching the found nodes after parsing has completed.
|
||||
* @returns {{}|*|vertices}
|
||||
*/
|
||||
export const getVertices = function () {
|
||||
return vertices
|
||||
}
|
||||
export const getVertices = function() {
|
||||
return vertices;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieval function for fetching the found links after parsing has completed.
|
||||
* @returns {{}|*|edges}
|
||||
*/
|
||||
export const getEdges = function () {
|
||||
return edges
|
||||
}
|
||||
export const getEdges = function() {
|
||||
return edges;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieval function for fetching the found class definitions after parsing has completed.
|
||||
* @returns {{}|*|classes}
|
||||
*/
|
||||
export const getClasses = function () {
|
||||
return classes
|
||||
}
|
||||
export const getClasses = function() {
|
||||
return classes;
|
||||
};
|
||||
|
||||
const setupToolTips = function (element) {
|
||||
let tooltipElem = d3.select('.mermaidTooltip')
|
||||
const setupToolTips = function(element) {
|
||||
let tooltipElem = d3.select('.mermaidTooltip');
|
||||
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
|
||||
tooltipElem = d3.select('body')
|
||||
tooltipElem = d3
|
||||
.select('body')
|
||||
.append('div')
|
||||
.attr('class', 'mermaidTooltip')
|
||||
.style('opacity', 0)
|
||||
.style('opacity', 0);
|
||||
}
|
||||
|
||||
const svg = d3.select(element).select('svg')
|
||||
const svg = d3.select(element).select('svg');
|
||||
|
||||
const nodes = svg.selectAll('g.node')
|
||||
const nodes = svg.selectAll('g.node');
|
||||
nodes
|
||||
.on('mouseover', function () {
|
||||
const el = d3.select(this)
|
||||
const title = el.attr('title')
|
||||
.on('mouseover', function() {
|
||||
const el = d3.select(this);
|
||||
const title = el.attr('title');
|
||||
// Dont try to draw a tooltip if no data is provided
|
||||
if (title === null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const rect = this.getBoundingClientRect()
|
||||
const rect = this.getBoundingClientRect();
|
||||
|
||||
tooltipElem.transition()
|
||||
tooltipElem
|
||||
.transition()
|
||||
.duration(200)
|
||||
.style('opacity', '.9')
|
||||
tooltipElem.html(el.attr('title'))
|
||||
.style('left', (rect.left + (rect.right - rect.left) / 2) + 'px')
|
||||
.style('top', (rect.top - 14 + document.body.scrollTop) + 'px')
|
||||
el.classed('hover', true)
|
||||
.style('opacity', '.9');
|
||||
tooltipElem
|
||||
.html(el.attr('title'))
|
||||
.style('left', rect.left + (rect.right - rect.left) / 2 + 'px')
|
||||
.style('top', rect.top - 14 + document.body.scrollTop + 'px');
|
||||
el.classed('hover', true);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
tooltipElem.transition()
|
||||
.on('mouseout', function() {
|
||||
tooltipElem
|
||||
.transition()
|
||||
.duration(500)
|
||||
.style('opacity', 0)
|
||||
const el = d3.select(this)
|
||||
el.classed('hover', false)
|
||||
})
|
||||
}
|
||||
funs.push(setupToolTips)
|
||||
.style('opacity', 0);
|
||||
const el = d3.select(this);
|
||||
el.classed('hover', false);
|
||||
});
|
||||
};
|
||||
funs.push(setupToolTips);
|
||||
|
||||
/**
|
||||
* Clears the internal graph db so that a new graph can be parsed.
|
||||
*/
|
||||
export const clear = function () {
|
||||
vertices = {}
|
||||
classes = {}
|
||||
edges = []
|
||||
funs = []
|
||||
funs.push(setupToolTips)
|
||||
subGraphs = []
|
||||
subGraphLookup = {}
|
||||
subCount = 0
|
||||
tooltips = []
|
||||
}
|
||||
export const clear = function() {
|
||||
vertices = {};
|
||||
classes = {};
|
||||
edges = [];
|
||||
funs = [];
|
||||
funs.push(setupToolTips);
|
||||
subGraphs = [];
|
||||
subGraphLookup = {};
|
||||
subCount = 0;
|
||||
tooltips = [];
|
||||
};
|
||||
/**
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export const defaultStyle = function () {
|
||||
return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;'
|
||||
}
|
||||
export const 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.
|
||||
*/
|
||||
export const addSubGraph = function (_id, list, _title) {
|
||||
let id = _id
|
||||
let title = _title
|
||||
export const addSubGraph = function(_id, list, _title) {
|
||||
let id = _id;
|
||||
let title = _title;
|
||||
if (_id === _title && _title.match(/\s/)) {
|
||||
id = undefined
|
||||
id = undefined;
|
||||
}
|
||||
function uniq (a) {
|
||||
const prims = { 'boolean': {}, 'number': {}, 'string': {} }
|
||||
const objs = []
|
||||
function uniq(a) {
|
||||
const prims = { boolean: {}, number: {}, string: {} };
|
||||
const objs = [];
|
||||
|
||||
return a.filter(function (item) {
|
||||
const type = typeof item
|
||||
return a.filter(function(item) {
|
||||
const type = typeof item;
|
||||
if (item.trim() === '') {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
if (type in prims) { return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true) } else { return objs.indexOf(item) >= 0 ? false : objs.push(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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let nodeList = []
|
||||
let nodeList = [];
|
||||
|
||||
nodeList = uniq(nodeList.concat.apply(nodeList, list))
|
||||
nodeList = uniq(nodeList.concat.apply(nodeList, list));
|
||||
for (let i = 0; i < nodeList.length; i++) {
|
||||
if (nodeList[i][0].match(/\d/)) nodeList[i] = 's' + nodeList[i]
|
||||
if (nodeList[i][0].match(/\d/)) nodeList[i] = 's' + nodeList[i];
|
||||
}
|
||||
|
||||
id = id || ('subGraph' + subCount)
|
||||
if (id[0].match(/\d/)) id = 's' + id
|
||||
title = title || ''
|
||||
title = sanitize(title)
|
||||
subCount = subCount + 1
|
||||
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] }
|
||||
subGraphs.push(subGraph)
|
||||
subGraphLookup[id] = subGraph
|
||||
return id
|
||||
}
|
||||
id = id || 'subGraph' + subCount;
|
||||
if (id[0].match(/\d/)) id = 's' + id;
|
||||
title = title || '';
|
||||
title = sanitize(title);
|
||||
subCount = subCount + 1;
|
||||
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] };
|
||||
subGraphs.push(subGraph);
|
||||
subGraphLookup[id] = subGraph;
|
||||
return id;
|
||||
};
|
||||
|
||||
const getPosForId = function (id) {
|
||||
const getPosForId = function(id) {
|
||||
for (let i = 0; i < subGraphs.length; i++) {
|
||||
if (subGraphs[i].id === id) {
|
||||
return i
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
let secCount = -1
|
||||
const posCrossRef = []
|
||||
const indexNodes2 = function (id, pos) {
|
||||
const nodes = subGraphs[pos].nodes
|
||||
secCount = secCount + 1
|
||||
return -1;
|
||||
};
|
||||
let secCount = -1;
|
||||
const posCrossRef = [];
|
||||
const indexNodes2 = function(id, pos) {
|
||||
const nodes = subGraphs[pos].nodes;
|
||||
secCount = secCount + 1;
|
||||
if (secCount > 2000) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
posCrossRef[secCount] = pos
|
||||
posCrossRef[secCount] = pos;
|
||||
// Check if match
|
||||
if (subGraphs[pos].id === id) {
|
||||
return {
|
||||
result: true,
|
||||
count: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let count = 0
|
||||
let posCount = 1
|
||||
let count = 0;
|
||||
let posCount = 1;
|
||||
while (count < nodes.length) {
|
||||
const childPos = getPosForId(nodes[count])
|
||||
const childPos = getPosForId(nodes[count]);
|
||||
// Ignore regular nodes (pos will be -1)
|
||||
if (childPos >= 0) {
|
||||
const res = indexNodes2(id, childPos)
|
||||
const res = indexNodes2(id, childPos);
|
||||
if (res.result) {
|
||||
return {
|
||||
result: true,
|
||||
count: posCount + res.count
|
||||
}
|
||||
};
|
||||
} else {
|
||||
posCount = posCount + res.count
|
||||
posCount = posCount + res.count;
|
||||
}
|
||||
}
|
||||
count = count + 1
|
||||
count = count + 1;
|
||||
}
|
||||
|
||||
return {
|
||||
result: false,
|
||||
count: posCount
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const getDepthFirstPos = function (pos) {
|
||||
return posCrossRef[pos]
|
||||
}
|
||||
export const indexNodes = function () {
|
||||
secCount = -1
|
||||
export const getDepthFirstPos = function(pos) {
|
||||
return posCrossRef[pos];
|
||||
};
|
||||
export const indexNodes = function() {
|
||||
secCount = -1;
|
||||
if (subGraphs.length > 0) {
|
||||
indexNodes2('none', subGraphs.length - 1, 0)
|
||||
indexNodes2('none', subGraphs.length - 1, 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getSubGraphs = function () {
|
||||
return subGraphs
|
||||
}
|
||||
export const getSubGraphs = function() {
|
||||
return subGraphs;
|
||||
};
|
||||
|
||||
export default {
|
||||
addVertex,
|
||||
@ -478,4 +492,4 @@ export default {
|
||||
getDepthFirstPos,
|
||||
indexNodes,
|
||||
getSubGraphs
|
||||
}
|
||||
};
|
||||
|
@ -1,274 +1,288 @@
|
||||
import graphlib from 'graphlibrary'
|
||||
import * as d3 from 'd3'
|
||||
import graphlib from 'graphlibrary';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
import flowDb from './flowDb'
|
||||
import flow from './parser/flow'
|
||||
import { getConfig } from '../../config'
|
||||
import dagreD3 from 'dagre-d3-renderer'
|
||||
import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js'
|
||||
import { logger } from '../../logger'
|
||||
import { interpolateToCurve } from '../../utils'
|
||||
import flowDb from './flowDb';
|
||||
import flow from './parser/flow';
|
||||
import { getConfig } from '../../config';
|
||||
import dagreD3 from 'dagre-d3-renderer';
|
||||
import addHtmlLabel from 'dagre-d3-renderer/lib/label/add-html-label.js';
|
||||
import { logger } from '../../logger';
|
||||
import { interpolateToCurve } from '../../utils';
|
||||
|
||||
const conf = {
|
||||
}
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf)
|
||||
const conf = {};
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
conf[keys[i]] = cnf[keys[i]]
|
||||
conf[keys[i]] = cnf[keys[i]];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found in the graph definition to the graph to be rendered.
|
||||
* @param vert Object containing the vertices.
|
||||
* @param g The graph that is to be drawn.
|
||||
*/
|
||||
export const addVertices = function (vert, g, svgId) {
|
||||
const svg = d3.select(`[id="${svgId}"]`)
|
||||
const keys = Object.keys(vert)
|
||||
export const addVertices = function(vert, g, svgId) {
|
||||
const svg = d3.select(`[id="${svgId}"]`);
|
||||
const keys = Object.keys(vert);
|
||||
|
||||
const styleFromStyleArr = function (styleStr, arr, { label }) {
|
||||
const styleFromStyleArr = function(styleStr, arr, { label }) {
|
||||
if (!label) {
|
||||
// Create a compound style definition from the style definitions found for the node in the graph definition
|
||||
// Create a compound style definition from the style definitions found for the node in the graph definition
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (typeof arr[i] !== 'undefined') {
|
||||
styleStr = styleStr + arr[i] + ';'
|
||||
styleStr = styleStr + arr[i] + ';';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (typeof arr[i] !== 'undefined') {
|
||||
if (arr[i].match('^color:')) styleStr = styleStr + arr[i] + ';'
|
||||
if (arr[i].match('^color:')) styleStr = styleStr + arr[i] + ';';
|
||||
}
|
||||
}
|
||||
}
|
||||
return styleStr
|
||||
}
|
||||
return styleStr;
|
||||
};
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
keys.forEach(function (id) {
|
||||
const vertex = vert[id]
|
||||
keys.forEach(function(id) {
|
||||
const vertex = vert[id];
|
||||
|
||||
/**
|
||||
* Variable for storing the classes for the vertex
|
||||
* @type {string}
|
||||
*/
|
||||
let classStr = ''
|
||||
let classStr = '';
|
||||
if (vertex.classes.length > 0) {
|
||||
classStr = vertex.classes.join(' ')
|
||||
classStr = vertex.classes.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Variable for storing the extracted style for the vertex
|
||||
* @type {string}
|
||||
*/
|
||||
let style = ''
|
||||
let style = '';
|
||||
// Create a compound style definition from the style definitions found for the node in the graph definition
|
||||
style = styleFromStyleArr(style, vertex.styles, { label: false })
|
||||
let labelStyle = ''
|
||||
labelStyle = styleFromStyleArr(labelStyle, vertex.styles, { label: true })
|
||||
style = styleFromStyleArr(style, vertex.styles, { label: false });
|
||||
let labelStyle = '';
|
||||
labelStyle = styleFromStyleArr(labelStyle, vertex.styles, { label: true });
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id
|
||||
let vertexText = vertex.text !== undefined ? vertex.text : vertex.id;
|
||||
|
||||
// We create a SVG label, either by delegating to addHtmlLabel or manually
|
||||
let vertexNode
|
||||
let vertexNode;
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||
const node = { label: vertexText.replace(/fa[lrsb]?:fa-[\w-]+/g, s => `<i class='${s.replace(':', ' ')}'></i>`) }
|
||||
vertexNode = addHtmlLabel(svg, node).node()
|
||||
vertexNode.parentNode.removeChild(vertexNode)
|
||||
const node = {
|
||||
label: vertexText.replace(
|
||||
/fa[lrsb]?:fa-[\w-]+/g,
|
||||
s => `<i class='${s.replace(':', ' ')}'></i>`
|
||||
)
|
||||
};
|
||||
vertexNode = addHtmlLabel(svg, node).node();
|
||||
vertexNode.parentNode.removeChild(vertexNode);
|
||||
} else {
|
||||
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text')
|
||||
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
|
||||
const rows = vertexText.split(/<br[/]{0,1}>/)
|
||||
const rows = vertexText.split(/<br[/]{0,1}>/);
|
||||
|
||||
for (let j = 0; j < rows.length; j++) {
|
||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan')
|
||||
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve')
|
||||
tspan.setAttribute('dy', '1em')
|
||||
tspan.setAttribute('x', '1')
|
||||
tspan.textContent = rows[j]
|
||||
svgLabel.appendChild(tspan)
|
||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||
tspan.setAttribute('dy', '1em');
|
||||
tspan.setAttribute('x', '1');
|
||||
tspan.textContent = rows[j];
|
||||
svgLabel.appendChild(tspan);
|
||||
}
|
||||
vertexNode = svgLabel
|
||||
vertexNode = svgLabel;
|
||||
}
|
||||
|
||||
// If the node has a link, we wrap it in a SVG link
|
||||
if (vertex.link) {
|
||||
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a')
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link)
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener')
|
||||
link.appendChild(vertexNode)
|
||||
vertexNode = link
|
||||
const link = document.createElementNS('http://www.w3.org/2000/svg', 'a');
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'href', vertex.link);
|
||||
link.setAttributeNS('http://www.w3.org/2000/svg', 'rel', 'noopener');
|
||||
link.appendChild(vertexNode);
|
||||
vertexNode = link;
|
||||
}
|
||||
|
||||
let radious = 0
|
||||
let _shape = ''
|
||||
let radious = 0;
|
||||
let _shape = '';
|
||||
// Set the shape based parameters
|
||||
switch (vertex.type) {
|
||||
case 'round':
|
||||
radious = 5
|
||||
_shape = 'rect'
|
||||
break
|
||||
radious = 5;
|
||||
_shape = 'rect';
|
||||
break;
|
||||
case 'square':
|
||||
_shape = 'rect'
|
||||
break
|
||||
_shape = 'rect';
|
||||
break;
|
||||
case 'diamond':
|
||||
_shape = 'question'
|
||||
break
|
||||
_shape = 'question';
|
||||
break;
|
||||
case 'odd':
|
||||
_shape = 'rect_left_inv_arrow'
|
||||
break
|
||||
_shape = 'rect_left_inv_arrow';
|
||||
break;
|
||||
case 'lean_right':
|
||||
_shape = 'lean_right'
|
||||
break
|
||||
_shape = 'lean_right';
|
||||
break;
|
||||
case 'lean_left':
|
||||
_shape = 'lean_left'
|
||||
break
|
||||
_shape = 'lean_left';
|
||||
break;
|
||||
case 'trapezoid':
|
||||
_shape = 'trapezoid'
|
||||
break
|
||||
_shape = 'trapezoid';
|
||||
break;
|
||||
case 'inv_trapezoid':
|
||||
_shape = 'inv_trapezoid'
|
||||
break
|
||||
_shape = 'inv_trapezoid';
|
||||
break;
|
||||
case 'odd_right':
|
||||
_shape = 'rect_left_inv_arrow'
|
||||
break
|
||||
_shape = 'rect_left_inv_arrow';
|
||||
break;
|
||||
case 'circle':
|
||||
_shape = 'circle'
|
||||
break
|
||||
_shape = 'circle';
|
||||
break;
|
||||
case 'ellipse':
|
||||
_shape = 'ellipse'
|
||||
break
|
||||
_shape = 'ellipse';
|
||||
break;
|
||||
case 'group':
|
||||
_shape = 'rect'
|
||||
break
|
||||
_shape = 'rect';
|
||||
break;
|
||||
default:
|
||||
_shape = 'rect'
|
||||
_shape = 'rect';
|
||||
}
|
||||
// Add the node
|
||||
g.setNode(vertex.id, { labelType: 'svg', labelStyle: labelStyle, shape: _shape, label: vertexNode, rx: radious, ry: radious, 'class': classStr, style: style, id: vertex.id })
|
||||
})
|
||||
}
|
||||
g.setNode(vertex.id, {
|
||||
labelType: 'svg',
|
||||
labelStyle: labelStyle,
|
||||
shape: _shape,
|
||||
label: vertexNode,
|
||||
rx: radious,
|
||||
ry: radious,
|
||||
class: classStr,
|
||||
style: style,
|
||||
id: vertex.id
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Add edges to graph based on parsed graph defninition
|
||||
* @param {Object} edges The edges to add to the graph
|
||||
* @param {Object} g The graph object
|
||||
*/
|
||||
export const addEdges = function (edges, g) {
|
||||
let cnt = 0
|
||||
export const addEdges = function(edges, g) {
|
||||
let cnt = 0;
|
||||
|
||||
let defaultStyle
|
||||
let defaultStyle;
|
||||
if (typeof edges.defaultStyle !== 'undefined') {
|
||||
defaultStyle = edges.defaultStyle.toString().replace(/,/g, ';')
|
||||
defaultStyle = edges.defaultStyle.toString().replace(/,/g, ';');
|
||||
}
|
||||
|
||||
edges.forEach(function (edge) {
|
||||
cnt++
|
||||
const edgeData = {}
|
||||
edges.forEach(function(edge) {
|
||||
cnt++;
|
||||
const edgeData = {};
|
||||
|
||||
// Set link type for rendering
|
||||
if (edge.type === 'arrow_open') {
|
||||
edgeData.arrowhead = 'none'
|
||||
edgeData.arrowhead = 'none';
|
||||
} else {
|
||||
edgeData.arrowhead = 'normal'
|
||||
edgeData.arrowhead = 'normal';
|
||||
}
|
||||
|
||||
let style = ''
|
||||
let style = '';
|
||||
if (typeof edge.style !== 'undefined') {
|
||||
edge.style.forEach(function (s) {
|
||||
style = style + s + ';'
|
||||
})
|
||||
edge.style.forEach(function(s) {
|
||||
style = style + s + ';';
|
||||
});
|
||||
} else {
|
||||
switch (edge.stroke) {
|
||||
case 'normal':
|
||||
style = 'fill:none'
|
||||
style = 'fill:none';
|
||||
if (typeof defaultStyle !== 'undefined') {
|
||||
style = defaultStyle
|
||||
style = defaultStyle;
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 'dotted':
|
||||
style = 'stroke: #333; fill:none;stroke-width:2px;stroke-dasharray:3;'
|
||||
break
|
||||
style = 'stroke: #333; fill:none;stroke-width:2px;stroke-dasharray:3;';
|
||||
break;
|
||||
case 'thick':
|
||||
style = 'stroke: #333; stroke-width: 3.5px;fill:none'
|
||||
break
|
||||
style = 'stroke: #333; stroke-width: 3.5px;fill:none';
|
||||
break;
|
||||
}
|
||||
}
|
||||
edgeData.style = style
|
||||
edgeData.style = style;
|
||||
|
||||
if (typeof edge.interpolate !== 'undefined') {
|
||||
edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear)
|
||||
edgeData.curve = interpolateToCurve(edge.interpolate, d3.curveLinear);
|
||||
} else if (typeof edges.defaultInterpolate !== 'undefined') {
|
||||
edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear)
|
||||
edgeData.curve = interpolateToCurve(edges.defaultInterpolate, d3.curveLinear);
|
||||
} else {
|
||||
edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear)
|
||||
edgeData.curve = interpolateToCurve(conf.curve, d3.curveLinear);
|
||||
}
|
||||
|
||||
if (typeof edge.text === 'undefined') {
|
||||
if (typeof edge.style !== 'undefined') {
|
||||
edgeData.arrowheadStyle = 'fill: #333'
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
}
|
||||
} else {
|
||||
edgeData.arrowheadStyle = 'fill: #333'
|
||||
edgeData.arrowheadStyle = 'fill: #333';
|
||||
if (typeof edge.style === 'undefined') {
|
||||
edgeData.labelpos = 'c'
|
||||
edgeData.labelpos = 'c';
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
edgeData.labelType = 'html'
|
||||
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>'
|
||||
edgeData.labelType = 'html';
|
||||
edgeData.label = '<span class="edgeLabel">' + edge.text + '</span>';
|
||||
} else {
|
||||
edgeData.labelType = 'text'
|
||||
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'
|
||||
edgeData.label = edge.text.replace(/<br>/g, '\n')
|
||||
edgeData.labelType = 'text';
|
||||
edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none';
|
||||
edgeData.label = edge.text.replace(/<br>/g, '\n');
|
||||
}
|
||||
} else {
|
||||
edgeData.label = edge.text.replace(/<br>/g, '\n')
|
||||
edgeData.label = edge.text.replace(/<br>/g, '\n');
|
||||
}
|
||||
}
|
||||
// Add the edge to the graph
|
||||
g.setEdge(edge.start, edge.end, edgeData, cnt)
|
||||
})
|
||||
}
|
||||
g.setEdge(edge.start, edge.end, edgeData, cnt);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the all the styles from classDef statements in the graph definition.
|
||||
* @returns {object} classDef styles
|
||||
*/
|
||||
export const getClasses = function (text) {
|
||||
logger.info('Extracting classes')
|
||||
flowDb.clear()
|
||||
const parser = flow.parser
|
||||
parser.yy = flowDb
|
||||
export const getClasses = function(text) {
|
||||
logger.info('Extracting classes');
|
||||
flowDb.clear();
|
||||
const parser = flow.parser;
|
||||
parser.yy = flowDb;
|
||||
|
||||
// Parse the graph definition
|
||||
parser.parse(text)
|
||||
return flowDb.getClasses()
|
||||
}
|
||||
parser.parse(text);
|
||||
return flowDb.getClasses();
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
export const draw = function (text, id) {
|
||||
logger.info('Drawing flowchart')
|
||||
flowDb.clear()
|
||||
const parser = flow.parser
|
||||
parser.yy = flowDb
|
||||
export const draw = function(text, id) {
|
||||
logger.info('Drawing flowchart');
|
||||
flowDb.clear();
|
||||
const parser = flow.parser;
|
||||
parser.yy = flowDb;
|
||||
|
||||
// Parse the graph definition
|
||||
try {
|
||||
parser.parse(text)
|
||||
parser.parse(text);
|
||||
} catch (err) {
|
||||
logger.debug('Parsing failed')
|
||||
logger.debug('Parsing failed');
|
||||
}
|
||||
|
||||
// Fetch the default direction, use TD if none was found
|
||||
let dir = flowDb.getDirection()
|
||||
let dir = flowDb.getDirection();
|
||||
if (typeof dir === 'undefined') {
|
||||
dir = 'TD'
|
||||
dir = 'TD';
|
||||
}
|
||||
|
||||
// Create the input mermaid.graph
|
||||
@ -280,196 +294,238 @@ export const draw = function (text, id) {
|
||||
rankdir: dir,
|
||||
marginx: 20,
|
||||
marginy: 20
|
||||
|
||||
})
|
||||
.setDefaultEdgeLabel(function () {
|
||||
return {}
|
||||
})
|
||||
.setDefaultEdgeLabel(function() {
|
||||
return {};
|
||||
});
|
||||
|
||||
let subG
|
||||
const subGraphs = flowDb.getSubGraphs()
|
||||
let subG;
|
||||
const subGraphs = flowDb.getSubGraphs();
|
||||
for (let i = subGraphs.length - 1; i >= 0; i--) {
|
||||
subG = subGraphs[i]
|
||||
flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes)
|
||||
subG = subGraphs[i];
|
||||
flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes);
|
||||
}
|
||||
|
||||
// Fetch the verices/nodes and edges/links from the parsed graph definition
|
||||
const vert = flowDb.getVertices()
|
||||
const vert = flowDb.getVertices();
|
||||
|
||||
const edges = flowDb.getEdges()
|
||||
const edges = flowDb.getEdges();
|
||||
|
||||
let i = 0
|
||||
let i = 0;
|
||||
for (i = subGraphs.length - 1; i >= 0; i--) {
|
||||
subG = subGraphs[i]
|
||||
subG = subGraphs[i];
|
||||
|
||||
d3.selectAll('cluster').append('text')
|
||||
d3.selectAll('cluster').append('text');
|
||||
|
||||
for (let j = 0; j < subG.nodes.length; j++) {
|
||||
g.setParent(subG.nodes[j], subG.id)
|
||||
g.setParent(subG.nodes[j], subG.id);
|
||||
}
|
||||
}
|
||||
addVertices(vert, g, id)
|
||||
addEdges(edges, g)
|
||||
addVertices(vert, g, id);
|
||||
addEdges(edges, g);
|
||||
|
||||
// Create the renderer
|
||||
const Render = dagreD3.render
|
||||
const render = new Render()
|
||||
const Render = dagreD3.render;
|
||||
const render = new Render();
|
||||
|
||||
// Add custom shape for rhombus type of boc (decision)
|
||||
render.shapes().question = function (parent, bbox, node) {
|
||||
const w = bbox.width
|
||||
const h = bbox.height
|
||||
const s = (w + h) * 0.9
|
||||
render.shapes().question = function(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const s = (w + h) * 0.9;
|
||||
const points = [
|
||||
{ x: s / 2, y: 0 },
|
||||
{ x: s, y: -s / 2 },
|
||||
{ x: s / 2, y: -s },
|
||||
{ x: 0, y: -s / 2 }
|
||||
]
|
||||
const shapeSvg = parent.insert('polygon', ':first-child')
|
||||
.attr('points', points.map(function (d) {
|
||||
return d.x + ',' + d.y
|
||||
}).join(' '))
|
||||
];
|
||||
const shapeSvg = parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('rx', 5)
|
||||
.attr('ry', 5)
|
||||
.attr('transform', 'translate(' + (-s / 2) + ',' + (s * 2 / 4) + ')')
|
||||
node.intersect = function (point) {
|
||||
return dagreD3.intersect.polygon(node, points, point)
|
||||
}
|
||||
return shapeSvg
|
||||
}
|
||||
.attr('transform', 'translate(' + -s / 2 + ',' + (s * 2) / 4 + ')');
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
render.shapes().rect_left_inv_arrow = function (parent, bbox, node) {
|
||||
const w = bbox.width
|
||||
const h = bbox.height
|
||||
render.shapes().rect_left_inv_arrow = function(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const points = [
|
||||
{ x: -h / 2, y: 0 },
|
||||
{ x: w, y: 0 },
|
||||
{ x: w, y: -h },
|
||||
{ x: -h / 2, y: -h },
|
||||
{ x: 0, y: -h / 2 }
|
||||
]
|
||||
const shapeSvg = parent.insert('polygon', ':first-child')
|
||||
.attr('points', points.map(function (d) {
|
||||
return d.x + ',' + d.y
|
||||
}).join(' '))
|
||||
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
|
||||
node.intersect = function (point) {
|
||||
return dagreD3.intersect.polygon(node, points, point)
|
||||
}
|
||||
return shapeSvg
|
||||
}
|
||||
];
|
||||
const shapeSvg = parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
render.shapes().lean_right = function (parent, bbox, node) {
|
||||
const w = bbox.width
|
||||
const h = bbox.height
|
||||
render.shapes().lean_right = function(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const points = [
|
||||
{ x: -2 * h / 6, y: 0 },
|
||||
{ x: (-2 * h) / 6, y: 0 },
|
||||
{ x: w - h / 6, y: 0 },
|
||||
{ x: w + 2 * h / 6, y: -h },
|
||||
{ x: w + (2 * h) / 6, y: -h },
|
||||
{ x: h / 6, y: -h }
|
||||
]
|
||||
const shapeSvg = parent.insert('polygon', ':first-child')
|
||||
.attr('points', points.map(function (d) {
|
||||
return d.x + ',' + d.y
|
||||
}).join(' '))
|
||||
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
|
||||
node.intersect = function (point) {
|
||||
return dagreD3.intersect.polygon(node, points, point)
|
||||
}
|
||||
return shapeSvg
|
||||
}
|
||||
];
|
||||
const shapeSvg = parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
render.shapes().lean_left = function (parent, bbox, node) {
|
||||
const w = bbox.width
|
||||
const h = bbox.height
|
||||
render.shapes().lean_left = function(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const points = [
|
||||
{ x: 2 * h / 6, y: 0 },
|
||||
{ x: (2 * h) / 6, y: 0 },
|
||||
{ x: w + h / 6, y: 0 },
|
||||
{ x: w - 2 * h / 6, y: -h },
|
||||
{ x: w - (2 * h) / 6, y: -h },
|
||||
{ x: -h / 6, y: -h }
|
||||
]
|
||||
const shapeSvg = parent.insert('polygon', ':first-child')
|
||||
.attr('points', points.map(function (d) {
|
||||
return d.x + ',' + d.y
|
||||
}).join(' '))
|
||||
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
|
||||
node.intersect = function (point) {
|
||||
return dagreD3.intersect.polygon(node, points, point)
|
||||
}
|
||||
return shapeSvg
|
||||
}
|
||||
];
|
||||
const shapeSvg = parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
render.shapes().trapezoid = function (parent, bbox, node) {
|
||||
const w = bbox.width
|
||||
const h = bbox.height
|
||||
render.shapes().trapezoid = function(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const points = [
|
||||
{ x: -2 * h / 6, y: 0 },
|
||||
{ x: w + 2 * h / 6, y: 0 },
|
||||
{ x: (-2 * h) / 6, y: 0 },
|
||||
{ x: w + (2 * h) / 6, y: 0 },
|
||||
{ x: w - h / 6, y: -h },
|
||||
{ x: h / 6, y: -h }
|
||||
]
|
||||
const shapeSvg = parent.insert('polygon', ':first-child')
|
||||
.attr('points', points.map(function (d) {
|
||||
return d.x + ',' + d.y
|
||||
}).join(' '))
|
||||
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
|
||||
node.intersect = function (point) {
|
||||
return dagreD3.intersect.polygon(node, points, point)
|
||||
}
|
||||
return shapeSvg
|
||||
}
|
||||
];
|
||||
const shapeSvg = parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
// Add custom shape for box with inverted arrow on left side
|
||||
render.shapes().inv_trapezoid = function (parent, bbox, node) {
|
||||
const w = bbox.width
|
||||
const h = bbox.height
|
||||
render.shapes().inv_trapezoid = function(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const points = [
|
||||
{ x: h / 6, y: 0 },
|
||||
{ x: w - h / 6, y: 0 },
|
||||
{ x: w + 2 * h / 6, y: -h },
|
||||
{ x: -2 * h / 6, y: -h }
|
||||
]
|
||||
const shapeSvg = parent.insert('polygon', ':first-child')
|
||||
.attr('points', points.map(function (d) {
|
||||
return d.x + ',' + d.y
|
||||
}).join(' '))
|
||||
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
|
||||
node.intersect = function (point) {
|
||||
return dagreD3.intersect.polygon(node, points, point)
|
||||
}
|
||||
return shapeSvg
|
||||
}
|
||||
{ x: w + (2 * h) / 6, y: -h },
|
||||
{ x: (-2 * h) / 6, y: -h }
|
||||
];
|
||||
const shapeSvg = parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
// Add custom shape for box with inverted arrow on right side
|
||||
render.shapes().rect_right_inv_arrow = function (parent, bbox, node) {
|
||||
const w = bbox.width
|
||||
const h = bbox.height
|
||||
render.shapes().rect_right_inv_arrow = function(parent, bbox, node) {
|
||||
const w = bbox.width;
|
||||
const h = bbox.height;
|
||||
const points = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: w + h / 2, y: 0 },
|
||||
{ x: w, y: -h / 2 },
|
||||
{ x: w + h / 2, y: -h },
|
||||
{ x: 0, y: -h }
|
||||
]
|
||||
const shapeSvg = parent.insert('polygon', ':first-child')
|
||||
.attr('points', points.map(function (d) {
|
||||
return d.x + ',' + d.y
|
||||
}).join(' '))
|
||||
.attr('transform', 'translate(' + (-w / 2) + ',' + (h * 2 / 4) + ')')
|
||||
node.intersect = function (point) {
|
||||
return dagreD3.intersect.polygon(node, points, point)
|
||||
}
|
||||
return shapeSvg
|
||||
}
|
||||
];
|
||||
const shapeSvg = parent
|
||||
.insert('polygon', ':first-child')
|
||||
.attr(
|
||||
'points',
|
||||
points
|
||||
.map(function(d) {
|
||||
return d.x + ',' + d.y;
|
||||
})
|
||||
.join(' ')
|
||||
)
|
||||
.attr('transform', 'translate(' + -w / 2 + ',' + (h * 2) / 4 + ')');
|
||||
node.intersect = function(point) {
|
||||
return dagreD3.intersect.polygon(node, points, point);
|
||||
};
|
||||
return shapeSvg;
|
||||
};
|
||||
|
||||
// Add our custom arrow - an empty arrowhead
|
||||
render.arrows().none = function normal (parent, id, edge, type) {
|
||||
const marker = parent.append('marker')
|
||||
render.arrows().none = function normal(parent, id, edge, type) {
|
||||
const marker = parent
|
||||
.append('marker')
|
||||
.attr('id', id)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
@ -477,16 +533,16 @@ export const draw = function (text, id) {
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.attr('orient', 'auto');
|
||||
|
||||
const path = marker.append('path')
|
||||
.attr('d', 'M 0 0 L 0 0 L 0 0 z')
|
||||
dagreD3.util.applyStyle(path, edge[type + 'Style'])
|
||||
}
|
||||
const path = marker.append('path').attr('d', 'M 0 0 L 0 0 L 0 0 z');
|
||||
dagreD3.util.applyStyle(path, edge[type + 'Style']);
|
||||
};
|
||||
|
||||
// Override normal arrowhead defined in d3. Remove style & add class to allow css styling.
|
||||
render.arrows().normal = function normal (parent, id, edge, type) {
|
||||
const marker = parent.append('marker')
|
||||
render.arrows().normal = function normal(parent, id, edge, type) {
|
||||
const marker = parent
|
||||
.append('marker')
|
||||
.attr('id', id)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 9)
|
||||
@ -494,76 +550,76 @@ export const draw = function (text, id) {
|
||||
.attr('markerUnits', 'strokeWidth')
|
||||
.attr('markerWidth', 8)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.attr('orient', 'auto');
|
||||
|
||||
marker.append('path')
|
||||
marker
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
||||
.attr('class', 'arrowheadPath')
|
||||
.style('stroke-width', 1)
|
||||
.style('stroke-dasharray', '1,0')
|
||||
}
|
||||
.style('stroke-dasharray', '1,0');
|
||||
};
|
||||
|
||||
// Set up an SVG group so that we can translate the final graph.
|
||||
const svg = d3.select(`[id="${id}"]`)
|
||||
const svg = d3.select(`[id="${id}"]`);
|
||||
|
||||
// Run the renderer. This is what draws the final graph.
|
||||
const element = d3.select('#' + id + ' g')
|
||||
render(element, g)
|
||||
const element = d3.select('#' + id + ' g');
|
||||
render(element, g);
|
||||
|
||||
element.selectAll('g.node')
|
||||
.attr('title', function () {
|
||||
return flowDb.getTooltip(this.id)
|
||||
})
|
||||
element.selectAll('g.node').attr('title', function() {
|
||||
return flowDb.getTooltip(this.id);
|
||||
});
|
||||
|
||||
const padding = 8
|
||||
const width = g.maxX - g.minX + padding * 2
|
||||
const height = g.maxY - g.minY + padding * 2
|
||||
svg.attr('width', '100%')
|
||||
svg.attr('style', `max-width: ${width}px;`)
|
||||
svg.attr('viewBox', `0 0 ${width} ${height}`)
|
||||
svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`)
|
||||
const padding = 8;
|
||||
const width = g.maxX - g.minX + padding * 2;
|
||||
const height = g.maxY - g.minY + padding * 2;
|
||||
svg.attr('width', '100%');
|
||||
svg.attr('style', `max-width: ${width}px;`);
|
||||
svg.attr('viewBox', `0 0 ${width} ${height}`);
|
||||
svg.select('g').attr('transform', `translate(${padding - g.minX}, ${padding - g.minY})`);
|
||||
|
||||
// Index nodes
|
||||
flowDb.indexNodes('subGraph' + i)
|
||||
flowDb.indexNodes('subGraph' + i);
|
||||
|
||||
// reposition labels
|
||||
for (i = 0; i < subGraphs.length; i++) {
|
||||
subG = subGraphs[i]
|
||||
subG = subGraphs[i];
|
||||
|
||||
if (subG.title !== 'undefined') {
|
||||
const clusterRects = document.querySelectorAll('#' + id + ' #' + subG.id + ' rect')
|
||||
const clusterEl = document.querySelectorAll('#' + id + ' #' + subG.id)
|
||||
const clusterRects = document.querySelectorAll('#' + id + ' #' + subG.id + ' rect');
|
||||
const clusterEl = document.querySelectorAll('#' + id + ' #' + subG.id);
|
||||
|
||||
const xPos = clusterRects[0].x.baseVal.value
|
||||
const yPos = clusterRects[0].y.baseVal.value
|
||||
const width = clusterRects[0].width.baseVal.value
|
||||
const cluster = d3.select(clusterEl[0])
|
||||
const te = cluster.select('.label')
|
||||
te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`)
|
||||
te.attr('id', id + 'Text')
|
||||
const xPos = clusterRects[0].x.baseVal.value;
|
||||
const yPos = clusterRects[0].y.baseVal.value;
|
||||
const width = clusterRects[0].width.baseVal.value;
|
||||
const cluster = d3.select(clusterEl[0]);
|
||||
const te = cluster.select('.label');
|
||||
te.attr('transform', `translate(${xPos + width / 2}, ${yPos + 14})`);
|
||||
te.attr('id', id + 'Text');
|
||||
}
|
||||
}
|
||||
|
||||
// Add label rects for non html labels
|
||||
if (!getConfig().flowchart.htmlLabels) {
|
||||
const labels = document.querySelectorAll('#' + id + ' .edgeLabel .label')
|
||||
const labels = document.querySelectorAll('#' + id + ' .edgeLabel .label');
|
||||
for (let k = 0; k < labels.length; k++) {
|
||||
const label = labels[k]
|
||||
const label = labels[k];
|
||||
|
||||
// Get dimensions of label
|
||||
const dim = label.getBBox()
|
||||
const dim = label.getBBox();
|
||||
|
||||
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
|
||||
rect.setAttribute('rx', 0)
|
||||
rect.setAttribute('ry', 0)
|
||||
rect.setAttribute('width', dim.width)
|
||||
rect.setAttribute('height', dim.height)
|
||||
rect.setAttribute('style', 'fill:#e8e8e8;')
|
||||
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
rect.setAttribute('rx', 0);
|
||||
rect.setAttribute('ry', 0);
|
||||
rect.setAttribute('width', dim.width);
|
||||
rect.setAttribute('height', dim.height);
|
||||
rect.setAttribute('style', 'fill:#e8e8e8;');
|
||||
|
||||
label.insertBefore(rect, label.firstChild)
|
||||
label.insertBefore(rect, label.firstChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
@ -571,4 +627,4 @@ export default {
|
||||
addEdges,
|
||||
getClasses,
|
||||
draw
|
||||
}
|
||||
};
|
||||
|
@ -1,38 +1,37 @@
|
||||
import flowDb from '../flowDb'
|
||||
import flow from './flow'
|
||||
import { setConfig } from '../../../config'
|
||||
import flowDb from '../flowDb';
|
||||
import flow from './flow';
|
||||
import { setConfig } from '../../../config';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
})
|
||||
securityLevel: 'strict'
|
||||
});
|
||||
|
||||
describe('when parsing flowcharts', function () {
|
||||
beforeEach(function () {
|
||||
flow.parser.yy = flowDb
|
||||
flow.parser.yy.clear()
|
||||
})
|
||||
describe('when parsing flowcharts', function() {
|
||||
beforeEach(function() {
|
||||
flow.parser.yy = flowDb;
|
||||
flow.parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should handle chaining of vertices', function () {
|
||||
it('should handle chaining of vertices', function() {
|
||||
const res = flow.parser.parse(`
|
||||
graph TD
|
||||
A-->B-->C;
|
||||
`);
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(vert['A'].id).toBe('A')
|
||||
expect(vert['B'].id).toBe('B')
|
||||
expect(vert['C'].id).toBe('C')
|
||||
expect(edges.length).toBe(2)
|
||||
expect(edges[0].start).toBe('A')
|
||||
expect(edges[0].end).toBe('B')
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
expect(edges[0].text).toBe('')
|
||||
expect(edges[1].start).toBe('B')
|
||||
expect(edges[1].end).toBe('C')
|
||||
expect(edges[1].type).toBe('arrow')
|
||||
expect(edges[1].text).toBe('')
|
||||
})
|
||||
|
||||
})
|
||||
expect(vert['A'].id).toBe('A');
|
||||
expect(vert['B'].id).toBe('B');
|
||||
expect(vert['C'].id).toBe('C');
|
||||
expect(edges.length).toBe(2);
|
||||
expect(edges[0].start).toBe('A');
|
||||
expect(edges[0].end).toBe('B');
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
expect(edges[0].text).toBe('');
|
||||
expect(edges[1].start).toBe('B');
|
||||
expect(edges[1].end).toBe('C');
|
||||
expect(edges[1].type).toBe('arrow');
|
||||
expect(edges[1].text).toBe('');
|
||||
});
|
||||
});
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,223 +1,223 @@
|
||||
import flowDb from '../flowDb'
|
||||
import flow from './flow'
|
||||
import { setConfig } from '../../../config'
|
||||
import flowDb from '../flowDb';
|
||||
import flow from './flow';
|
||||
import { setConfig } from '../../../config';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
})
|
||||
securityLevel: 'strict'
|
||||
});
|
||||
|
||||
describe('when parsing subgraphs', function () {
|
||||
beforeEach(function () {
|
||||
flow.parser.yy = flowDb
|
||||
flow.parser.yy.clear()
|
||||
})
|
||||
it('should handle subgraph with tab indentation', function () {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2\nend')
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
expect(subgraph.nodes.length).toBe(2)
|
||||
expect(subgraph.nodes[0]).toBe('a2')
|
||||
expect(subgraph.nodes[1]).toBe('a1')
|
||||
expect(subgraph.title).toBe('One')
|
||||
expect(subgraph.id).toBe('One')
|
||||
})
|
||||
it('should handle subgraph with chaining nodes indentation', function () {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2-->a3\nend')
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
expect(subgraph.nodes.length).toBe(3)
|
||||
expect(subgraph.nodes[0]).toBe('a3')
|
||||
expect(subgraph.nodes[1]).toBe('a2')
|
||||
expect(subgraph.nodes[2]).toBe('a1')
|
||||
expect(subgraph.title).toBe('One')
|
||||
expect(subgraph.id).toBe('One')
|
||||
})
|
||||
|
||||
it('should handle subgraph with multiple words in title', function () {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph "Some Title"\n\ta1-->a2\nend')
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
expect(subgraph.nodes.length).toBe(2)
|
||||
expect(subgraph.nodes[0]).toBe('a2')
|
||||
expect(subgraph.nodes[1]).toBe('a1')
|
||||
expect(subgraph.title).toBe('Some Title')
|
||||
expect(subgraph.id).toBe('subGraph0')
|
||||
describe('when parsing subgraphs', function() {
|
||||
beforeEach(function() {
|
||||
flow.parser.yy = flowDb;
|
||||
flow.parser.yy.clear();
|
||||
});
|
||||
it('should handle subgraph with tab indentation', function() {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2\nend');
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
expect(subgraph.nodes.length).toBe(2);
|
||||
expect(subgraph.nodes[0]).toBe('a2');
|
||||
expect(subgraph.nodes[1]).toBe('a1');
|
||||
expect(subgraph.title).toBe('One');
|
||||
expect(subgraph.id).toBe('One');
|
||||
});
|
||||
it('should handle subgraph with chaining nodes indentation', function() {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph One\n\ta1-->a2-->a3\nend');
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
expect(subgraph.nodes.length).toBe(3);
|
||||
expect(subgraph.nodes[0]).toBe('a3');
|
||||
expect(subgraph.nodes[1]).toBe('a2');
|
||||
expect(subgraph.nodes[2]).toBe('a1');
|
||||
expect(subgraph.title).toBe('One');
|
||||
expect(subgraph.id).toBe('One');
|
||||
});
|
||||
|
||||
it('should handle subgraph with id and title notation', function () {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph some-id[Some Title]\n\ta1-->a2\nend')
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
expect(subgraph.nodes.length).toBe(2)
|
||||
expect(subgraph.nodes[0]).toBe('a2')
|
||||
expect(subgraph.nodes[1]).toBe('a1')
|
||||
expect(subgraph.title).toBe('Some Title')
|
||||
expect(subgraph.id).toBe('some-id')
|
||||
it('should handle subgraph with multiple words in title', function() {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph "Some Title"\n\ta1-->a2\nend');
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
expect(subgraph.nodes.length).toBe(2);
|
||||
expect(subgraph.nodes[0]).toBe('a2');
|
||||
expect(subgraph.nodes[1]).toBe('a1');
|
||||
expect(subgraph.title).toBe('Some Title');
|
||||
expect(subgraph.id).toBe('subGraph0');
|
||||
});
|
||||
|
||||
xit('should handle subgraph without id and space in title', function () {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph Some Title\n\ta1-->a2\nend')
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
expect(subgraph.nodes.length).toBe(2)
|
||||
expect(subgraph.nodes[0]).toBe('a1')
|
||||
expect(subgraph.nodes[1]).toBe('a2')
|
||||
expect(subgraph.title).toBe('Some Title')
|
||||
expect(subgraph.id).toBe('some-id')
|
||||
it('should handle subgraph with id and title notation', function() {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph some-id[Some Title]\n\ta1-->a2\nend');
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
expect(subgraph.nodes.length).toBe(2);
|
||||
expect(subgraph.nodes[0]).toBe('a2');
|
||||
expect(subgraph.nodes[1]).toBe('a1');
|
||||
expect(subgraph.title).toBe('Some Title');
|
||||
expect(subgraph.id).toBe('some-id');
|
||||
});
|
||||
|
||||
it('should handle subgraph id starting with a number', function () {
|
||||
xit('should handle subgraph without id and space in title', function() {
|
||||
const res = flow.parser.parse('graph TB\nsubgraph Some Title\n\ta1-->a2\nend');
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
expect(subgraph.nodes.length).toBe(2);
|
||||
expect(subgraph.nodes[0]).toBe('a1');
|
||||
expect(subgraph.nodes[1]).toBe('a2');
|
||||
expect(subgraph.title).toBe('Some Title');
|
||||
expect(subgraph.id).toBe('some-id');
|
||||
});
|
||||
|
||||
it('should handle subgraph id starting with a number', function() {
|
||||
const res = flow.parser.parse(`graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
subgraph 1test
|
||||
A
|
||||
end`)
|
||||
end`);
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
expect(subgraph.nodes.length).toBe(1)
|
||||
expect(subgraph.nodes[0]).toBe('A')
|
||||
expect(subgraph.id).toBe('s1test')
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
expect(subgraph.nodes.length).toBe(1);
|
||||
expect(subgraph.nodes[0]).toBe('A');
|
||||
expect(subgraph.id).toBe('s1test');
|
||||
});
|
||||
|
||||
it('should handle subgraphs1', function () {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph myTitle;c-->d;end;')
|
||||
it('should handle subgraphs1', function() {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph myTitle;c-->d;end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
it('should handle subgraphs with title in quotes', function () {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph "title in quotes";c-->d;end;')
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
it('should handle subgraphs with title in quotes', function() {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph "title in quotes";c-->d;end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.title).toBe('title in quotes')
|
||||
expect(subgraph.title).toBe('title in quotes');
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
it('should handle subgraphs in old style that was broken', function () {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph old style that is broken;c-->d;end;')
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
it('should handle subgraphs in old style that was broken', function() {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph old style that is broken;c-->d;end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.title).toBe('old style that is broken')
|
||||
expect(subgraph.title).toBe('old style that is broken');
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
it('should handle subgraphs with dashes in the title', function () {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph a-b-c;c-->d;end;')
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
it('should handle subgraphs with dashes in the title', function() {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph a-b-c;c-->d;end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.title).toBe('a-b-c')
|
||||
expect(subgraph.title).toBe('a-b-c');
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
it('should handle subgraphs with id and title in brackets', function () {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph uid1[text of doom];c-->d;end;')
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
it('should handle subgraphs with id and title in brackets', function() {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph uid1[text of doom];c-->d;end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.title).toBe('text of doom')
|
||||
expect(subgraph.id).toBe('uid1')
|
||||
expect(subgraph.title).toBe('text of doom');
|
||||
expect(subgraph.id).toBe('uid1');
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
it('should handle subgraphs with id and title in brackets and quotes', function () {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph uid2["text of doom"];c-->d;end;')
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
it('should handle subgraphs with id and title in brackets and quotes', function() {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph uid2["text of doom"];c-->d;end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.title).toBe('text of doom')
|
||||
expect(subgraph.id).toBe('uid2')
|
||||
expect(subgraph.title).toBe('text of doom');
|
||||
expect(subgraph.id).toBe('uid2');
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
it('should handle subgraphs with id and title in brackets without spaces', function () {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph uid2[textofdoom];c-->d;end;')
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
it('should handle subgraphs with id and title in brackets without spaces', function() {
|
||||
const res = flow.parser.parse('graph TD;A-->B;subgraph uid2[textofdoom];c-->d;end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
const subgraphs = flow.parser.yy.getSubGraphs()
|
||||
expect(subgraphs.length).toBe(1)
|
||||
const subgraph = subgraphs[0]
|
||||
const subgraphs = flow.parser.yy.getSubGraphs();
|
||||
expect(subgraphs.length).toBe(1);
|
||||
const subgraph = subgraphs[0];
|
||||
|
||||
expect(subgraph.title).toBe('textofdoom')
|
||||
expect(subgraph.id).toBe('uid2')
|
||||
expect(subgraph.title).toBe('textofdoom');
|
||||
expect(subgraph.id).toBe('uid2');
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
|
||||
it('should handle subgraphs2', function () {
|
||||
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\n\n c-->d \nend\n')
|
||||
it('should handle subgraphs2', function() {
|
||||
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\n\n c-->d \nend\n');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
|
||||
it('should handle nested subgraphs', function () {
|
||||
const str = 'graph TD\n' +
|
||||
'A-->B\n' +
|
||||
'subgraph myTitle\n\n' +
|
||||
' c-->d \n\n' +
|
||||
' subgraph inner\n\n e-->f \n end \n\n' +
|
||||
' subgraph inner\n\n h-->i \n end \n\n' +
|
||||
'end\n'
|
||||
const res = flow.parser.parse(str)
|
||||
})
|
||||
it('should handle nested subgraphs', function() {
|
||||
const str =
|
||||
'graph TD\n' +
|
||||
'A-->B\n' +
|
||||
'subgraph myTitle\n\n' +
|
||||
' c-->d \n\n' +
|
||||
' subgraph inner\n\n e-->f \n end \n\n' +
|
||||
' subgraph inner\n\n h-->i \n end \n\n' +
|
||||
'end\n';
|
||||
const res = flow.parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle subgraphs4', function () {
|
||||
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend;')
|
||||
it('should handle subgraphs4', function() {
|
||||
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-->d\nend;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
|
||||
it('should handle subgraphs5', function () {
|
||||
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-- text -->d\nd-->e\n end;')
|
||||
it('should handle subgraphs5', function() {
|
||||
const res = flow.parser.parse('graph TD\nA-->B\nsubgraph myTitle\nc-- text -->d\nd-->e\n end;');
|
||||
|
||||
const vert = flow.parser.yy.getVertices()
|
||||
const edges = flow.parser.yy.getEdges()
|
||||
const vert = flow.parser.yy.getVertices();
|
||||
const edges = flow.parser.yy.getEdges();
|
||||
|
||||
expect(edges[0].type).toBe('arrow')
|
||||
})
|
||||
|
||||
})
|
||||
expect(edges[0].type).toBe('arrow');
|
||||
});
|
||||
});
|
||||
|
@ -1,217 +1,214 @@
|
||||
import moment from 'moment-mini'
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url'
|
||||
import { logger } from '../../logger'
|
||||
import { getConfig } from '../../config'
|
||||
import moment from 'moment-mini';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import { logger } from '../../logger';
|
||||
import { getConfig } from '../../config';
|
||||
|
||||
const config = getConfig()
|
||||
let dateFormat = ''
|
||||
let axisFormat = ''
|
||||
let excludes = []
|
||||
let title = ''
|
||||
let sections = []
|
||||
let tasks = []
|
||||
let currentSection = ''
|
||||
const tags = ['active', 'done', 'crit', 'milestone']
|
||||
let funs = []
|
||||
let inclusiveEndDates = false
|
||||
const config = getConfig();
|
||||
let dateFormat = '';
|
||||
let axisFormat = '';
|
||||
let excludes = [];
|
||||
let title = '';
|
||||
let sections = [];
|
||||
let tasks = [];
|
||||
let currentSection = '';
|
||||
const tags = ['active', 'done', 'crit', 'milestone'];
|
||||
let funs = [];
|
||||
let inclusiveEndDates = false;
|
||||
|
||||
export const clear = function () {
|
||||
sections = []
|
||||
tasks = []
|
||||
currentSection = ''
|
||||
funs = []
|
||||
title = ''
|
||||
taskCnt = 0
|
||||
lastTask = undefined
|
||||
lastTaskID = undefined
|
||||
rawTasks = []
|
||||
dateFormat = ''
|
||||
axisFormat = ''
|
||||
excludes = []
|
||||
inclusiveEndDates = false
|
||||
}
|
||||
export const clear = function() {
|
||||
sections = [];
|
||||
tasks = [];
|
||||
currentSection = '';
|
||||
funs = [];
|
||||
title = '';
|
||||
taskCnt = 0;
|
||||
lastTask = undefined;
|
||||
lastTaskID = undefined;
|
||||
rawTasks = [];
|
||||
dateFormat = '';
|
||||
axisFormat = '';
|
||||
excludes = [];
|
||||
inclusiveEndDates = false;
|
||||
};
|
||||
|
||||
export const setAxisFormat = function (txt) {
|
||||
axisFormat = txt
|
||||
}
|
||||
export const setAxisFormat = function(txt) {
|
||||
axisFormat = txt;
|
||||
};
|
||||
|
||||
export const getAxisFormat = function () {
|
||||
return axisFormat
|
||||
}
|
||||
export const getAxisFormat = function() {
|
||||
return axisFormat;
|
||||
};
|
||||
|
||||
export const setDateFormat = function (txt) {
|
||||
dateFormat = txt
|
||||
}
|
||||
export const setDateFormat = function(txt) {
|
||||
dateFormat = txt;
|
||||
};
|
||||
|
||||
export const enableInclusiveEndDates = function () {
|
||||
inclusiveEndDates = true
|
||||
}
|
||||
export const enableInclusiveEndDates = function() {
|
||||
inclusiveEndDates = true;
|
||||
};
|
||||
|
||||
export const endDatesAreInclusive = function () {
|
||||
return inclusiveEndDates
|
||||
}
|
||||
export const endDatesAreInclusive = function() {
|
||||
return inclusiveEndDates;
|
||||
};
|
||||
|
||||
export const getDateFormat = function () {
|
||||
return dateFormat
|
||||
}
|
||||
export const getDateFormat = function() {
|
||||
return dateFormat;
|
||||
};
|
||||
|
||||
export const setExcludes = function (txt) {
|
||||
excludes = txt.toLowerCase().split(/[\s,]+/)
|
||||
}
|
||||
export const setExcludes = function(txt) {
|
||||
excludes = txt.toLowerCase().split(/[\s,]+/);
|
||||
};
|
||||
|
||||
export const getExcludes = function () {
|
||||
return excludes
|
||||
}
|
||||
export const getExcludes = function() {
|
||||
return excludes;
|
||||
};
|
||||
|
||||
export const setTitle = function (txt) {
|
||||
title = txt
|
||||
}
|
||||
export const setTitle = function(txt) {
|
||||
title = txt;
|
||||
};
|
||||
|
||||
export const getTitle = function () {
|
||||
return title
|
||||
}
|
||||
export const getTitle = function() {
|
||||
return title;
|
||||
};
|
||||
|
||||
export const addSection = function (txt) {
|
||||
currentSection = txt
|
||||
sections.push(txt)
|
||||
}
|
||||
export const addSection = function(txt) {
|
||||
currentSection = txt;
|
||||
sections.push(txt);
|
||||
};
|
||||
|
||||
export const getSections = function () {
|
||||
return sections
|
||||
}
|
||||
export const getSections = function() {
|
||||
return sections;
|
||||
};
|
||||
|
||||
export const getTasks = function () {
|
||||
let allItemsPricessed = compileTasks()
|
||||
const maxDepth = 10
|
||||
let iterationCount = 0
|
||||
while (!allItemsPricessed && (iterationCount < maxDepth)) {
|
||||
allItemsPricessed = compileTasks()
|
||||
iterationCount++
|
||||
export const getTasks = function() {
|
||||
let allItemsPricessed = compileTasks();
|
||||
const maxDepth = 10;
|
||||
let iterationCount = 0;
|
||||
while (!allItemsPricessed && iterationCount < maxDepth) {
|
||||
allItemsPricessed = compileTasks();
|
||||
iterationCount++;
|
||||
}
|
||||
|
||||
tasks = rawTasks
|
||||
tasks = rawTasks;
|
||||
|
||||
return tasks
|
||||
}
|
||||
return tasks;
|
||||
};
|
||||
|
||||
const isInvalidDate = function (date, dateFormat, excludes) {
|
||||
const isInvalidDate = function(date, dateFormat, excludes) {
|
||||
if (date.isoWeekday() >= 6 && excludes.indexOf('weekends') >= 0) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
if (excludes.indexOf(date.format('dddd').toLowerCase()) >= 0) {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
return excludes.indexOf(date.format(dateFormat.trim())) >= 0
|
||||
}
|
||||
return excludes.indexOf(date.format(dateFormat.trim())) >= 0;
|
||||
};
|
||||
|
||||
const checkTaskDates = function (task, dateFormat, excludes) {
|
||||
if (!excludes.length || task.manualEndTime) return
|
||||
let startTime = moment(task.startTime, dateFormat, true)
|
||||
startTime.add(1, 'd')
|
||||
let endTime = moment(task.endTime, dateFormat, true)
|
||||
let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes)
|
||||
task.endTime = endTime.toDate()
|
||||
task.renderEndTime = renderEndTime
|
||||
}
|
||||
const checkTaskDates = function(task, dateFormat, excludes) {
|
||||
if (!excludes.length || task.manualEndTime) return;
|
||||
let startTime = moment(task.startTime, dateFormat, true);
|
||||
startTime.add(1, 'd');
|
||||
let endTime = moment(task.endTime, dateFormat, true);
|
||||
let renderEndTime = fixTaskDates(startTime, endTime, dateFormat, excludes);
|
||||
task.endTime = endTime.toDate();
|
||||
task.renderEndTime = renderEndTime;
|
||||
};
|
||||
|
||||
const fixTaskDates = function (startTime, endTime, dateFormat, excludes) {
|
||||
let invalid = false
|
||||
let renderEndTime = null
|
||||
const fixTaskDates = function(startTime, endTime, dateFormat, excludes) {
|
||||
let invalid = false;
|
||||
let renderEndTime = null;
|
||||
while (startTime.date() <= endTime.date()) {
|
||||
if (!invalid) {
|
||||
renderEndTime = endTime.toDate()
|
||||
renderEndTime = endTime.toDate();
|
||||
}
|
||||
invalid = isInvalidDate(startTime, dateFormat, excludes)
|
||||
invalid = isInvalidDate(startTime, dateFormat, excludes);
|
||||
if (invalid) {
|
||||
endTime.add(1, 'd')
|
||||
endTime.add(1, 'd');
|
||||
}
|
||||
startTime.add(1, 'd')
|
||||
startTime.add(1, 'd');
|
||||
}
|
||||
return renderEndTime
|
||||
}
|
||||
return renderEndTime;
|
||||
};
|
||||
|
||||
const getStartDate = function (prevTime, dateFormat, str) {
|
||||
str = str.trim()
|
||||
const getStartDate = function(prevTime, dateFormat, str) {
|
||||
str = str.trim();
|
||||
|
||||
// Test for after
|
||||
const re = /^after\s+([\d\w-]+)/
|
||||
const afterStatement = re.exec(str.trim())
|
||||
const re = /^after\s+([\d\w-]+)/;
|
||||
const afterStatement = re.exec(str.trim());
|
||||
|
||||
if (afterStatement !== null) {
|
||||
const task = findTaskById(afterStatement[1])
|
||||
const task = findTaskById(afterStatement[1]);
|
||||
|
||||
if (typeof task === 'undefined') {
|
||||
const dt = new Date()
|
||||
dt.setHours(0, 0, 0, 0)
|
||||
return dt
|
||||
const dt = new Date();
|
||||
dt.setHours(0, 0, 0, 0);
|
||||
return dt;
|
||||
}
|
||||
return task.endTime
|
||||
return task.endTime;
|
||||
}
|
||||
|
||||
// Check for actual date set
|
||||
let mDate = moment(str, dateFormat.trim(), true)
|
||||
let mDate = moment(str, dateFormat.trim(), true);
|
||||
if (mDate.isValid()) {
|
||||
return mDate.toDate()
|
||||
return mDate.toDate();
|
||||
} else {
|
||||
logger.debug('Invalid date:' + str)
|
||||
logger.debug('With date format:' + dateFormat.trim())
|
||||
logger.debug('Invalid date:' + str);
|
||||
logger.debug('With date format:' + dateFormat.trim());
|
||||
}
|
||||
|
||||
// Default date - now
|
||||
return new Date()
|
||||
}
|
||||
return new Date();
|
||||
};
|
||||
|
||||
const durationToDate = function (durationStatement, relativeTime) {
|
||||
const durationToDate = function(durationStatement, relativeTime) {
|
||||
if (durationStatement !== null) {
|
||||
switch (durationStatement[2]) {
|
||||
case 's':
|
||||
relativeTime.add(durationStatement[1], 'seconds')
|
||||
break
|
||||
relativeTime.add(durationStatement[1], 'seconds');
|
||||
break;
|
||||
case 'm':
|
||||
relativeTime.add(durationStatement[1], 'minutes')
|
||||
break
|
||||
relativeTime.add(durationStatement[1], 'minutes');
|
||||
break;
|
||||
case 'h':
|
||||
relativeTime.add(durationStatement[1], 'hours')
|
||||
break
|
||||
relativeTime.add(durationStatement[1], 'hours');
|
||||
break;
|
||||
case 'd':
|
||||
relativeTime.add(durationStatement[1], 'days')
|
||||
break
|
||||
relativeTime.add(durationStatement[1], 'days');
|
||||
break;
|
||||
case 'w':
|
||||
relativeTime.add(durationStatement[1], 'weeks')
|
||||
break
|
||||
relativeTime.add(durationStatement[1], 'weeks');
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Default date - now
|
||||
return relativeTime.toDate()
|
||||
}
|
||||
return relativeTime.toDate();
|
||||
};
|
||||
|
||||
const getEndDate = function (prevTime, dateFormat, str, inclusive) {
|
||||
inclusive = inclusive || false
|
||||
str = str.trim()
|
||||
const getEndDate = function(prevTime, dateFormat, str, inclusive) {
|
||||
inclusive = inclusive || false;
|
||||
str = str.trim();
|
||||
|
||||
// Check for actual date
|
||||
let mDate = moment(str, dateFormat.trim(), true)
|
||||
let mDate = moment(str, dateFormat.trim(), true);
|
||||
if (mDate.isValid()) {
|
||||
if (inclusive) {
|
||||
mDate.add(1, 'd')
|
||||
mDate.add(1, 'd');
|
||||
}
|
||||
return mDate.toDate()
|
||||
return mDate.toDate();
|
||||
}
|
||||
|
||||
return durationToDate(
|
||||
/^([\d]+)([wdhms])/.exec(str.trim()),
|
||||
moment(prevTime)
|
||||
)
|
||||
}
|
||||
return durationToDate(/^([\d]+)([wdhms])/.exec(str.trim()), moment(prevTime));
|
||||
};
|
||||
|
||||
let taskCnt = 0
|
||||
const parseId = function (idStr) {
|
||||
let taskCnt = 0;
|
||||
const parseId = function(idStr) {
|
||||
if (typeof idStr === 'undefined') {
|
||||
taskCnt = taskCnt + 1
|
||||
return 'task' + taskCnt
|
||||
taskCnt = taskCnt + 1;
|
||||
return 'task' + taskCnt;
|
||||
}
|
||||
return idStr
|
||||
}
|
||||
return idStr;
|
||||
};
|
||||
// id, startDate, endDate
|
||||
// id, startDate, length
|
||||
// id, after x, endDate
|
||||
@ -223,116 +220,116 @@ const parseId = function (idStr) {
|
||||
// endDate
|
||||
// length
|
||||
|
||||
const compileData = function (prevTask, dataStr) {
|
||||
let ds
|
||||
const compileData = function(prevTask, dataStr) {
|
||||
let ds;
|
||||
|
||||
if (dataStr.substr(0, 1) === ':') {
|
||||
ds = dataStr.substr(1, dataStr.length)
|
||||
ds = dataStr.substr(1, dataStr.length);
|
||||
} else {
|
||||
ds = dataStr
|
||||
ds = dataStr;
|
||||
}
|
||||
|
||||
const data = ds.split(',')
|
||||
const data = ds.split(',');
|
||||
|
||||
const task = {}
|
||||
const task = {};
|
||||
|
||||
// Get tags like active, done, crit and milestone
|
||||
getTaskTags(data, task, tags)
|
||||
getTaskTags(data, task, tags);
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i] = data[i].trim()
|
||||
data[i] = data[i].trim();
|
||||
}
|
||||
|
||||
let endTimeData = ''
|
||||
let endTimeData = '';
|
||||
switch (data.length) {
|
||||
case 1:
|
||||
task.id = parseId()
|
||||
task.startTime = prevTask.endTime
|
||||
endTimeData = data[0]
|
||||
break
|
||||
task.id = parseId();
|
||||
task.startTime = prevTask.endTime;
|
||||
endTimeData = data[0];
|
||||
break;
|
||||
case 2:
|
||||
task.id = parseId()
|
||||
task.startTime = getStartDate(undefined, dateFormat, data[0])
|
||||
endTimeData = data[1]
|
||||
break
|
||||
task.id = parseId();
|
||||
task.startTime = getStartDate(undefined, dateFormat, data[0]);
|
||||
endTimeData = data[1];
|
||||
break;
|
||||
case 3:
|
||||
task.id = parseId(data[0])
|
||||
task.startTime = getStartDate(undefined, dateFormat, data[1])
|
||||
endTimeData = data[2]
|
||||
break
|
||||
task.id = parseId(data[0]);
|
||||
task.startTime = getStartDate(undefined, dateFormat, data[1]);
|
||||
endTimeData = data[2];
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
if (endTimeData) {
|
||||
task.endTime = getEndDate(task.startTime, dateFormat, endTimeData, inclusiveEndDates)
|
||||
task.manualEndTime = moment(endTimeData, 'YYYY-MM-DD', true).isValid()
|
||||
checkTaskDates(task, dateFormat, excludes)
|
||||
task.endTime = getEndDate(task.startTime, dateFormat, endTimeData, inclusiveEndDates);
|
||||
task.manualEndTime = moment(endTimeData, 'YYYY-MM-DD', true).isValid();
|
||||
checkTaskDates(task, dateFormat, excludes);
|
||||
}
|
||||
|
||||
return task
|
||||
}
|
||||
return task;
|
||||
};
|
||||
|
||||
const parseData = function (prevTaskId, dataStr) {
|
||||
let ds
|
||||
const parseData = function(prevTaskId, dataStr) {
|
||||
let ds;
|
||||
if (dataStr.substr(0, 1) === ':') {
|
||||
ds = dataStr.substr(1, dataStr.length)
|
||||
ds = dataStr.substr(1, dataStr.length);
|
||||
} else {
|
||||
ds = dataStr
|
||||
ds = dataStr;
|
||||
}
|
||||
|
||||
const data = ds.split(',')
|
||||
const data = ds.split(',');
|
||||
|
||||
const task = {}
|
||||
const task = {};
|
||||
|
||||
// Get tags like active, done, crit and milestone
|
||||
getTaskTags(data, task, tags)
|
||||
getTaskTags(data, task, tags);
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
data[i] = data[i].trim()
|
||||
data[i] = data[i].trim();
|
||||
}
|
||||
|
||||
switch (data.length) {
|
||||
case 1:
|
||||
task.id = parseId()
|
||||
task.id = parseId();
|
||||
task.startTime = {
|
||||
type: 'prevTaskEnd',
|
||||
id: prevTaskId
|
||||
}
|
||||
};
|
||||
task.endTime = {
|
||||
data: data[0]
|
||||
}
|
||||
break
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
task.id = parseId()
|
||||
task.id = parseId();
|
||||
task.startTime = {
|
||||
type: 'getStartDate',
|
||||
startData: data[0]
|
||||
}
|
||||
};
|
||||
task.endTime = {
|
||||
data: data[1]
|
||||
}
|
||||
break
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
task.id = parseId(data[0])
|
||||
task.id = parseId(data[0]);
|
||||
task.startTime = {
|
||||
type: 'getStartDate',
|
||||
startData: data[1]
|
||||
}
|
||||
};
|
||||
task.endTime = {
|
||||
data: data[2]
|
||||
}
|
||||
break
|
||||
};
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return task
|
||||
}
|
||||
return task;
|
||||
};
|
||||
|
||||
let lastTask
|
||||
let lastTaskID
|
||||
let rawTasks = []
|
||||
const taskDb = {}
|
||||
export const addTask = function (descr, data) {
|
||||
let lastTask;
|
||||
let lastTaskID;
|
||||
let rawTasks = [];
|
||||
const taskDb = {};
|
||||
export const addTask = function(descr, data) {
|
||||
const rawTask = {
|
||||
section: currentSection,
|
||||
type: currentSection,
|
||||
@ -342,174 +339,187 @@ export const addTask = function (descr, data) {
|
||||
raw: { data: data },
|
||||
task: descr,
|
||||
classes: []
|
||||
}
|
||||
const taskInfo = parseData(lastTaskID, data)
|
||||
rawTask.raw.startTime = taskInfo.startTime
|
||||
rawTask.raw.endTime = taskInfo.endTime
|
||||
rawTask.id = taskInfo.id
|
||||
rawTask.prevTaskId = lastTaskID
|
||||
rawTask.active = taskInfo.active
|
||||
rawTask.done = taskInfo.done
|
||||
rawTask.crit = taskInfo.crit
|
||||
rawTask.milestone = taskInfo.milestone
|
||||
};
|
||||
const taskInfo = parseData(lastTaskID, data);
|
||||
rawTask.raw.startTime = taskInfo.startTime;
|
||||
rawTask.raw.endTime = taskInfo.endTime;
|
||||
rawTask.id = taskInfo.id;
|
||||
rawTask.prevTaskId = lastTaskID;
|
||||
rawTask.active = taskInfo.active;
|
||||
rawTask.done = taskInfo.done;
|
||||
rawTask.crit = taskInfo.crit;
|
||||
rawTask.milestone = taskInfo.milestone;
|
||||
|
||||
const pos = rawTasks.push(rawTask)
|
||||
const pos = rawTasks.push(rawTask);
|
||||
|
||||
lastTaskID = rawTask.id
|
||||
lastTaskID = rawTask.id;
|
||||
// Store cross ref
|
||||
taskDb[rawTask.id] = pos - 1
|
||||
}
|
||||
taskDb[rawTask.id] = pos - 1;
|
||||
};
|
||||
|
||||
export const findTaskById = function (id) {
|
||||
const pos = taskDb[id]
|
||||
return rawTasks[pos]
|
||||
}
|
||||
export const findTaskById = function(id) {
|
||||
const pos = taskDb[id];
|
||||
return rawTasks[pos];
|
||||
};
|
||||
|
||||
export const addTaskOrg = function (descr, data) {
|
||||
export const addTaskOrg = function(descr, data) {
|
||||
const newTask = {
|
||||
section: currentSection,
|
||||
type: currentSection,
|
||||
description: descr,
|
||||
task: descr,
|
||||
classes: []
|
||||
}
|
||||
const taskInfo = compileData(lastTask, data)
|
||||
newTask.startTime = taskInfo.startTime
|
||||
newTask.endTime = taskInfo.endTime
|
||||
newTask.id = taskInfo.id
|
||||
newTask.active = taskInfo.active
|
||||
newTask.done = taskInfo.done
|
||||
newTask.crit = taskInfo.crit
|
||||
newTask.milestone = taskInfo.milestone
|
||||
lastTask = newTask
|
||||
tasks.push(newTask)
|
||||
}
|
||||
};
|
||||
const taskInfo = compileData(lastTask, data);
|
||||
newTask.startTime = taskInfo.startTime;
|
||||
newTask.endTime = taskInfo.endTime;
|
||||
newTask.id = taskInfo.id;
|
||||
newTask.active = taskInfo.active;
|
||||
newTask.done = taskInfo.done;
|
||||
newTask.crit = taskInfo.crit;
|
||||
newTask.milestone = taskInfo.milestone;
|
||||
lastTask = newTask;
|
||||
tasks.push(newTask);
|
||||
};
|
||||
|
||||
const compileTasks = function () {
|
||||
const compileTask = function (pos) {
|
||||
const task = rawTasks[pos]
|
||||
let startTime = ''
|
||||
const compileTasks = function() {
|
||||
const compileTask = function(pos) {
|
||||
const task = rawTasks[pos];
|
||||
let startTime = '';
|
||||
switch (rawTasks[pos].raw.startTime.type) {
|
||||
case 'prevTaskEnd':
|
||||
const prevTask = findTaskById(task.prevTaskId)
|
||||
task.startTime = prevTask.endTime
|
||||
break
|
||||
const prevTask = findTaskById(task.prevTaskId);
|
||||
task.startTime = prevTask.endTime;
|
||||
break;
|
||||
case 'getStartDate':
|
||||
startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData)
|
||||
startTime = getStartDate(undefined, dateFormat, rawTasks[pos].raw.startTime.startData);
|
||||
if (startTime) {
|
||||
rawTasks[pos].startTime = startTime
|
||||
rawTasks[pos].startTime = startTime;
|
||||
}
|
||||
break
|
||||
break;
|
||||
}
|
||||
|
||||
if (rawTasks[pos].startTime) {
|
||||
rawTasks[pos].endTime = getEndDate(rawTasks[pos].startTime, dateFormat, rawTasks[pos].raw.endTime.data, inclusiveEndDates)
|
||||
rawTasks[pos].endTime = getEndDate(
|
||||
rawTasks[pos].startTime,
|
||||
dateFormat,
|
||||
rawTasks[pos].raw.endTime.data,
|
||||
inclusiveEndDates
|
||||
);
|
||||
if (rawTasks[pos].endTime) {
|
||||
rawTasks[pos].processed = true
|
||||
rawTasks[pos].manualEndTime = moment(rawTasks[pos].raw.endTime.data, 'YYYY-MM-DD', true).isValid()
|
||||
checkTaskDates(rawTasks[pos], dateFormat, excludes)
|
||||
rawTasks[pos].processed = true;
|
||||
rawTasks[pos].manualEndTime = moment(
|
||||
rawTasks[pos].raw.endTime.data,
|
||||
'YYYY-MM-DD',
|
||||
true
|
||||
).isValid();
|
||||
checkTaskDates(rawTasks[pos], dateFormat, excludes);
|
||||
}
|
||||
}
|
||||
|
||||
return rawTasks[pos].processed
|
||||
}
|
||||
return rawTasks[pos].processed;
|
||||
};
|
||||
|
||||
let allProcessed = true
|
||||
let allProcessed = true;
|
||||
for (let i = 0; i < rawTasks.length; i++) {
|
||||
compileTask(i)
|
||||
compileTask(i);
|
||||
|
||||
allProcessed = allProcessed && rawTasks[i].processed
|
||||
allProcessed = allProcessed && rawTasks[i].processed;
|
||||
}
|
||||
return allProcessed
|
||||
}
|
||||
return allProcessed;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a link is found. Adds the URL to the vertex data.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param linkStr URL to create a link for
|
||||
*/
|
||||
export const setLink = function (ids, _linkStr) {
|
||||
let linkStr = _linkStr
|
||||
export const setLink = function(ids, _linkStr) {
|
||||
let linkStr = _linkStr;
|
||||
if (config.securityLevel !== 'loose') {
|
||||
linkStr = sanitizeUrl(_linkStr)
|
||||
linkStr = sanitizeUrl(_linkStr);
|
||||
}
|
||||
ids.split(',').forEach(function (id) {
|
||||
let rawTask = findTaskById(id)
|
||||
ids.split(',').forEach(function(id) {
|
||||
let rawTask = findTaskById(id);
|
||||
if (typeof rawTask !== 'undefined') {
|
||||
pushFun(id, () => { window.open(linkStr, '_self') })
|
||||
pushFun(id, () => {
|
||||
window.open(linkStr, '_self');
|
||||
});
|
||||
}
|
||||
})
|
||||
setClass(ids, 'clickable')
|
||||
}
|
||||
});
|
||||
setClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a special node is found, e.g. a clickable element.
|
||||
* @param ids Comma separated list of ids
|
||||
* @param className Class to add
|
||||
*/
|
||||
export const setClass = function (ids, className) {
|
||||
ids.split(',').forEach(function (id) {
|
||||
let rawTask = findTaskById(id)
|
||||
export const setClass = function(ids, className) {
|
||||
ids.split(',').forEach(function(id) {
|
||||
let rawTask = findTaskById(id);
|
||||
if (typeof rawTask !== 'undefined') {
|
||||
rawTask.classes.push(className)
|
||||
rawTask.classes.push(className);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const setClickFun = function (id, functionName, functionArgs) {
|
||||
const setClickFun = function(id, functionName, functionArgs) {
|
||||
if (config.securityLevel !== 'loose') {
|
||||
return
|
||||
return;
|
||||
}
|
||||
if (typeof functionName === 'undefined') {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
let argList = []
|
||||
let argList = [];
|
||||
if (typeof functionArgs === 'string') {
|
||||
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
|
||||
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/)
|
||||
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
|
||||
for (let i = 0; i < argList.length; i++) {
|
||||
let item = argList[i].trim()
|
||||
let item = argList[i].trim();
|
||||
/* Removes all double quotes at the start and end of an argument */
|
||||
/* This preserves all starting and ending whitespace inside */
|
||||
if (item.charAt(0) === '"' && item.charAt(item.length - 1) === '"') {
|
||||
item = item.substr(1, item.length - 2)
|
||||
item = item.substr(1, item.length - 2);
|
||||
}
|
||||
argList[i] = item
|
||||
argList[i] = item;
|
||||
}
|
||||
}
|
||||
|
||||
let rawTask = findTaskById(id)
|
||||
let rawTask = findTaskById(id);
|
||||
if (typeof rawTask !== 'undefined') {
|
||||
pushFun(id, () => { window[functionName](...argList) })
|
||||
pushFun(id, () => {
|
||||
window[functionName](...argList);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The callbackFunction is executed in a click event bound to the task with the specified id or the task's assigned text
|
||||
* @param id The task's id
|
||||
* @param callbackFunction A function to be executed when clicked on the task or the task's text
|
||||
*/
|
||||
const pushFun = function (id, callbackFunction) {
|
||||
funs.push(function (element) {
|
||||
const pushFun = function(id, callbackFunction) {
|
||||
funs.push(function(element) {
|
||||
// const elem = d3.select(element).select(`[id="${id}"]`)
|
||||
const elem = document.querySelector(`[id="${id}"]`)
|
||||
const elem = document.querySelector(`[id="${id}"]`);
|
||||
if (elem !== null) {
|
||||
elem.addEventListener('click', function () {
|
||||
callbackFunction()
|
||||
})
|
||||
elem.addEventListener('click', function() {
|
||||
callbackFunction();
|
||||
});
|
||||
}
|
||||
})
|
||||
funs.push(function (element) {
|
||||
});
|
||||
funs.push(function(element) {
|
||||
// const elem = d3.select(element).select(`[id="${id}-text"]`)
|
||||
const elem = document.querySelector(`[id="${id}-text"]`)
|
||||
const elem = document.querySelector(`[id="${id}-text"]`);
|
||||
if (elem !== null) {
|
||||
elem.addEventListener('click', function () {
|
||||
callbackFunction()
|
||||
})
|
||||
elem.addEventListener('click', function() {
|
||||
callbackFunction();
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a click definition is found. Registers an event handler.
|
||||
@ -517,22 +527,22 @@ const pushFun = function (id, callbackFunction) {
|
||||
* @param functionName Function to be called on click
|
||||
* @param functionArgs Function args the function should be called with
|
||||
*/
|
||||
export const setClickEvent = function (ids, functionName, functionArgs) {
|
||||
ids.split(',').forEach(function (id) {
|
||||
setClickFun(id, functionName, functionArgs)
|
||||
})
|
||||
setClass(ids, 'clickable')
|
||||
}
|
||||
export const setClickEvent = function(ids, functionName, functionArgs) {
|
||||
ids.split(',').forEach(function(id) {
|
||||
setClickFun(id, functionName, functionArgs);
|
||||
});
|
||||
setClass(ids, 'clickable');
|
||||
};
|
||||
|
||||
/**
|
||||
* Binds all functions previously added to fun (specified through click) to the element
|
||||
* @param element
|
||||
*/
|
||||
export const bindFunctions = function (element) {
|
||||
funs.forEach(function (fun) {
|
||||
fun(element)
|
||||
})
|
||||
}
|
||||
export const bindFunctions = function(element) {
|
||||
funs.forEach(function(fun) {
|
||||
fun(element);
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
clear,
|
||||
@ -556,20 +566,20 @@ export default {
|
||||
setLink,
|
||||
bindFunctions,
|
||||
durationToDate
|
||||
}
|
||||
};
|
||||
|
||||
function getTaskTags (data, task, tags) {
|
||||
let matchFound = true
|
||||
function getTaskTags(data, task, tags) {
|
||||
let matchFound = true;
|
||||
while (matchFound) {
|
||||
matchFound = false
|
||||
tags.forEach(function (t) {
|
||||
const pattern = '^\\s*' + t + '\\s*$'
|
||||
const regex = new RegExp(pattern)
|
||||
matchFound = false;
|
||||
tags.forEach(function(t) {
|
||||
const pattern = '^\\s*' + t + '\\s*$';
|
||||
const regex = new RegExp(pattern);
|
||||
if (data[0].match(regex)) {
|
||||
task[t] = true
|
||||
data.shift(1)
|
||||
matchFound = true
|
||||
task[t] = true;
|
||||
data.shift(1);
|
||||
matchFound = true;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,32 @@
|
||||
/* eslint-env jasmine */
|
||||
import moment from 'moment-mini'
|
||||
import ganttDb from './ganttDb'
|
||||
import moment from 'moment-mini';
|
||||
import ganttDb from './ganttDb';
|
||||
|
||||
describe('when using the ganttDb', function () {
|
||||
beforeEach(function () {
|
||||
ganttDb.clear()
|
||||
})
|
||||
describe('when using the ganttDb', function() {
|
||||
beforeEach(function() {
|
||||
ganttDb.clear();
|
||||
});
|
||||
|
||||
describe('when using relative times', function () {
|
||||
describe('when using relative times', function() {
|
||||
it.each`
|
||||
diff | date | expected
|
||||
${' 1d'} | ${moment('2019-01-01')} | ${moment('2019-01-02').toDate()}
|
||||
${' 1w'} | ${moment('2019-01-01')} | ${moment('2019-01-08').toDate()}
|
||||
diff | date | expected
|
||||
${' 1d'} | ${moment('2019-01-01')} | ${moment('2019-01-02').toDate()}
|
||||
${' 1w'} | ${moment('2019-01-01')} | ${moment('2019-01-08').toDate()}
|
||||
`('should add $diff to $date resulting in $expected', ({ diff, date, expected }) => {
|
||||
expect(ganttDb.durationToDate(diff, date)).toEqual(expected)
|
||||
})
|
||||
})
|
||||
expect(ganttDb.durationToDate(diff, date)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when calling the clear function', function () {
|
||||
beforeEach(function () {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD')
|
||||
ganttDb.enableInclusiveEndDates()
|
||||
ganttDb.setExcludes('weekends 2019-02-06,friday')
|
||||
ganttDb.addSection('weekends skip test')
|
||||
ganttDb.addTask('test1', 'id1,2019-02-01,1d')
|
||||
ganttDb.addTask('test2', 'id2,after id1,2d')
|
||||
ganttDb.clear()
|
||||
})
|
||||
describe('when calling the clear function', function() {
|
||||
beforeEach(function() {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.enableInclusiveEndDates();
|
||||
ganttDb.setExcludes('weekends 2019-02-06,friday');
|
||||
ganttDb.addSection('weekends skip test');
|
||||
ganttDb.addTask('test1', 'id1,2019-02-01,1d');
|
||||
ganttDb.addTask('test2', 'id2,after id1,2d');
|
||||
ganttDb.clear();
|
||||
});
|
||||
|
||||
it.each`
|
||||
fn | expected
|
||||
@ -38,9 +38,9 @@ describe('when using the ganttDb', function () {
|
||||
${'getSections'} | ${[]}
|
||||
${'endDatesAreInclusive'} | ${false}
|
||||
`('should clear $fn', ({ fn, expected }) => {
|
||||
expect(ganttDb[ fn ]()).toEqual(expected)
|
||||
})
|
||||
})
|
||||
expect(ganttDb[fn]()).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it.each`
|
||||
testName | section | taskName | taskData | expStartDate | expEndDate | expId | expTask
|
||||
@ -52,135 +52,148 @@ describe('when using the ganttDb', function () {
|
||||
${'should handle duration (weeks) instead of fixed date to determine end date'} | ${'testa1'} | ${'test1'} | ${'id1,2013-01-01,2w'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 15)} | ${'id1'} | ${'test1'}
|
||||
${'should handle fixed dates without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,2013-01-12'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 12)} | ${'task1'} | ${'test1'}
|
||||
${'should handle duration instead of a fixed date to determine end date without id'} | ${'testa1'} | ${'test1'} | ${'2013-01-01,4d'} | ${new Date(2013, 0, 1)} | ${new Date(2013, 0, 5)} | ${'task1'} | ${'test1'}
|
||||
`('$testName', ({ section, taskName, taskData, expStartDate, expEndDate, expId, expTask }) => {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD')
|
||||
ganttDb.addSection(section)
|
||||
ganttDb.addTask(taskName, taskData)
|
||||
const tasks = ganttDb.getTasks()
|
||||
expect(tasks[0].startTime).toEqual(expStartDate)
|
||||
expect(tasks[0].endTime).toEqual(expEndDate)
|
||||
expect(tasks[0].id).toEqual(expId)
|
||||
expect(tasks[0].task).toEqual(expTask)
|
||||
})
|
||||
`('$testName', ({ section, taskName, taskData, expStartDate, expEndDate, expId, expTask }) => {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.addSection(section);
|
||||
ganttDb.addTask(taskName, taskData);
|
||||
const tasks = ganttDb.getTasks();
|
||||
expect(tasks[0].startTime).toEqual(expStartDate);
|
||||
expect(tasks[0].endTime).toEqual(expEndDate);
|
||||
expect(tasks[0].id).toEqual(expId);
|
||||
expect(tasks[0].task).toEqual(expTask);
|
||||
});
|
||||
|
||||
it.each`
|
||||
section | taskName1 | taskName2 | taskData1 | taskData2 | expStartDate2 | expEndDate2 | expId2 | expTask2
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'id2'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id3,1d'} | ${new Date((new Date()).setHours(0, 0, 0, 0))} | ${undefined} | ${'id2'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'task1'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2013-01-26'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 26)} | ${'task1'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2d'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 17)} | ${'task1'} | ${'test2'}
|
||||
`('$testName', ({ section, taskName1, taskName2, taskData1, taskData2, expStartDate2, expEndDate2, expId2, expTask2 }) => {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD')
|
||||
ganttDb.addSection(section)
|
||||
ganttDb.addTask(taskName1, taskData1)
|
||||
ganttDb.addTask(taskName2, taskData2)
|
||||
const tasks = ganttDb.getTasks()
|
||||
expect(tasks[1].startTime).toEqual(expStartDate2)
|
||||
if (!expEndDate2 === undefined) {
|
||||
expect(tasks[1].endTime).toEqual(expEndDate2)
|
||||
}
|
||||
expect(tasks[1].id).toEqual(expId2)
|
||||
expect(tasks[1].task).toEqual(expTask2)
|
||||
})
|
||||
section | taskName1 | taskName2 | taskData1 | taskData2 | expStartDate2 | expEndDate2 | expId2 | expTask2
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'id2'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'id2,after id3,1d'} | ${new Date(new Date().setHours(0, 0, 0, 0))} | ${undefined} | ${'id2'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'after id1,1d'} | ${new Date(2013, 0, 15)} | ${undefined} | ${'task1'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2013-01-26'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 26)} | ${'task1'} | ${'test2'}
|
||||
${'testa1'} | ${'test1'} | ${'test2'} | ${'id1,2013-01-01,2w'} | ${'2d'} | ${new Date(2013, 0, 15)} | ${new Date(2013, 0, 17)} | ${'task1'} | ${'test2'}
|
||||
`(
|
||||
'$testName',
|
||||
({
|
||||
section,
|
||||
taskName1,
|
||||
taskName2,
|
||||
taskData1,
|
||||
taskData2,
|
||||
expStartDate2,
|
||||
expEndDate2,
|
||||
expId2,
|
||||
expTask2
|
||||
}) => {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.addSection(section);
|
||||
ganttDb.addTask(taskName1, taskData1);
|
||||
ganttDb.addTask(taskName2, taskData2);
|
||||
const tasks = ganttDb.getTasks();
|
||||
expect(tasks[1].startTime).toEqual(expStartDate2);
|
||||
if (!expEndDate2 === undefined) {
|
||||
expect(tasks[1].endTime).toEqual(expEndDate2);
|
||||
}
|
||||
expect(tasks[1].id).toEqual(expId2);
|
||||
expect(tasks[1].task).toEqual(expTask2);
|
||||
}
|
||||
);
|
||||
|
||||
it('should handle relative start date based on id regardless of sections', function () {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD')
|
||||
ganttDb.addSection('testa1')
|
||||
ganttDb.addTask('test1', 'id1,2013-01-01,2w')
|
||||
ganttDb.addTask('test2', 'id2,after id3,1d')
|
||||
ganttDb.addSection('testa2')
|
||||
ganttDb.addTask('test3', 'id3,after id1,2d')
|
||||
it('should handle relative start date based on id regardless of sections', function() {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.addSection('testa1');
|
||||
ganttDb.addTask('test1', 'id1,2013-01-01,2w');
|
||||
ganttDb.addTask('test2', 'id2,after id3,1d');
|
||||
ganttDb.addSection('testa2');
|
||||
ganttDb.addTask('test3', 'id3,after id1,2d');
|
||||
|
||||
const tasks = ganttDb.getTasks()
|
||||
const tasks = ganttDb.getTasks();
|
||||
|
||||
expect(tasks[1].startTime).toEqual(new Date(2013, 0, 17))
|
||||
expect(tasks[1].endTime).toEqual(new Date(2013, 0, 18))
|
||||
expect(tasks[1].id).toEqual('id2')
|
||||
expect(tasks[1].task).toEqual('test2')
|
||||
expect(tasks[1].startTime).toEqual(new Date(2013, 0, 17));
|
||||
expect(tasks[1].endTime).toEqual(new Date(2013, 0, 18));
|
||||
expect(tasks[1].id).toEqual('id2');
|
||||
expect(tasks[1].task).toEqual('test2');
|
||||
|
||||
expect(tasks[2].id).toEqual('id3')
|
||||
expect(tasks[2].task).toEqual('test3')
|
||||
expect(tasks[2].startTime).toEqual(new Date(2013, 0, 15))
|
||||
expect(tasks[2].endTime).toEqual(new Date(2013, 0, 17))
|
||||
})
|
||||
it('should ignore weekends', function () {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD')
|
||||
ganttDb.setExcludes('weekends 2019-02-06,friday')
|
||||
ganttDb.addSection('weekends skip test')
|
||||
ganttDb.addTask('test1', 'id1,2019-02-01,1d')
|
||||
ganttDb.addTask('test2', 'id2,after id1,2d')
|
||||
ganttDb.addTask('test3', 'id3,after id2,7d')
|
||||
ganttDb.addTask('test4', 'id4,2019-02-01,2019-02-20') // Fixed endTime
|
||||
ganttDb.addTask('test5', 'id5,after id4,1d')
|
||||
ganttDb.addSection('full ending taks on last day')
|
||||
ganttDb.addTask('test6', 'id6,2019-02-13,2d')
|
||||
ganttDb.addTask('test7', 'id7,after id6,1d')
|
||||
expect(tasks[2].id).toEqual('id3');
|
||||
expect(tasks[2].task).toEqual('test3');
|
||||
expect(tasks[2].startTime).toEqual(new Date(2013, 0, 15));
|
||||
expect(tasks[2].endTime).toEqual(new Date(2013, 0, 17));
|
||||
});
|
||||
it('should ignore weekends', function() {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.setExcludes('weekends 2019-02-06,friday');
|
||||
ganttDb.addSection('weekends skip test');
|
||||
ganttDb.addTask('test1', 'id1,2019-02-01,1d');
|
||||
ganttDb.addTask('test2', 'id2,after id1,2d');
|
||||
ganttDb.addTask('test3', 'id3,after id2,7d');
|
||||
ganttDb.addTask('test4', 'id4,2019-02-01,2019-02-20'); // Fixed endTime
|
||||
ganttDb.addTask('test5', 'id5,after id4,1d');
|
||||
ganttDb.addSection('full ending taks on last day');
|
||||
ganttDb.addTask('test6', 'id6,2019-02-13,2d');
|
||||
ganttDb.addTask('test7', 'id7,after id6,1d');
|
||||
|
||||
const tasks = ganttDb.getTasks()
|
||||
const tasks = ganttDb.getTasks();
|
||||
|
||||
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[0].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[0].renderEndTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[0].id).toEqual('id1')
|
||||
expect(tasks[0].task).toEqual('test1')
|
||||
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[0].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[0].renderEndTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[0].id).toEqual('id1');
|
||||
expect(tasks[0].task).toEqual('test1');
|
||||
|
||||
expect(tasks[1].startTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[1].endTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[1].renderEndTime).toEqual(moment('2019-02-06', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[1].id).toEqual('id2')
|
||||
expect(tasks[1].task).toEqual('test2')
|
||||
expect(tasks[1].startTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].endTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].renderEndTime).toEqual(moment('2019-02-06', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].id).toEqual('id2');
|
||||
expect(tasks[1].task).toEqual('test2');
|
||||
|
||||
expect(tasks[2].startTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[2].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[2].renderEndTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[2].id).toEqual('id3')
|
||||
expect(tasks[2].task).toEqual('test3')
|
||||
expect(tasks[2].startTime).toEqual(moment('2019-02-07', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[2].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[2].renderEndTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[2].id).toEqual('id3');
|
||||
expect(tasks[2].task).toEqual('test3');
|
||||
|
||||
expect(tasks[3].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[3].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[3].renderEndTime).toBeNull() // Fixed end
|
||||
expect(tasks[3].id).toEqual('id4')
|
||||
expect(tasks[3].task).toEqual('test4')
|
||||
expect(tasks[3].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[3].endTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[3].renderEndTime).toBeNull(); // Fixed end
|
||||
expect(tasks[3].id).toEqual('id4');
|
||||
expect(tasks[3].task).toEqual('test4');
|
||||
|
||||
expect(tasks[4].startTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[4].endTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[4].renderEndTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[4].id).toEqual('id5')
|
||||
expect(tasks[4].task).toEqual('test5')
|
||||
expect(tasks[4].startTime).toEqual(moment('2019-02-20', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[4].endTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[4].renderEndTime).toEqual(moment('2019-02-21', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[4].id).toEqual('id5');
|
||||
expect(tasks[4].task).toEqual('test5');
|
||||
|
||||
expect(tasks[5].startTime).toEqual(moment('2019-02-13', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[5].endTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[5].renderEndTime).toEqual(moment('2019-02-15', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[5].id).toEqual('id6')
|
||||
expect(tasks[5].task).toEqual('test6')
|
||||
expect(tasks[5].startTime).toEqual(moment('2019-02-13', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[5].endTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[5].renderEndTime).toEqual(moment('2019-02-15', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[5].id).toEqual('id6');
|
||||
expect(tasks[5].task).toEqual('test6');
|
||||
|
||||
expect(tasks[6].startTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[6].endTime).toEqual(moment('2019-02-19', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[6].id).toEqual('id7')
|
||||
expect(tasks[6].task).toEqual('test7')
|
||||
})
|
||||
expect(tasks[6].startTime).toEqual(moment('2019-02-18', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[6].endTime).toEqual(moment('2019-02-19', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[6].id).toEqual('id7');
|
||||
expect(tasks[6].task).toEqual('test7');
|
||||
});
|
||||
|
||||
describe('when setting inclusive end dates', function () {
|
||||
beforeEach(function () {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD')
|
||||
ganttDb.enableInclusiveEndDates()
|
||||
ganttDb.addTask('test1', 'id1,2019-02-01,1d')
|
||||
ganttDb.addTask('test2', 'id2,2019-02-01,2019-02-03')
|
||||
})
|
||||
it('should automatically add one day to all end dates', function () {
|
||||
const tasks = ganttDb.getTasks()
|
||||
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[0].endTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[0].id).toEqual('id1')
|
||||
expect(tasks[0].task).toEqual('test1')
|
||||
describe('when setting inclusive end dates', function() {
|
||||
beforeEach(function() {
|
||||
ganttDb.setDateFormat('YYYY-MM-DD');
|
||||
ganttDb.enableInclusiveEndDates();
|
||||
ganttDb.addTask('test1', 'id1,2019-02-01,1d');
|
||||
ganttDb.addTask('test2', 'id2,2019-02-01,2019-02-03');
|
||||
});
|
||||
it('should automatically add one day to all end dates', function() {
|
||||
const tasks = ganttDb.getTasks();
|
||||
expect(tasks[0].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[0].endTime).toEqual(moment('2019-02-02', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[0].id).toEqual('id1');
|
||||
expect(tasks[0].task).toEqual('test1');
|
||||
|
||||
expect(tasks[1].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[1].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate())
|
||||
expect(tasks[1].renderEndTime).toBeNull() // Fixed end
|
||||
expect(tasks[1].manualEndTime).toBeTruthy()
|
||||
expect(tasks[1].id).toEqual('id2')
|
||||
expect(tasks[1].task).toEqual('test2')
|
||||
})
|
||||
})
|
||||
})
|
||||
expect(tasks[1].startTime).toEqual(moment('2019-02-01', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].endTime).toEqual(moment('2019-02-04', 'YYYY-MM-DD').toDate());
|
||||
expect(tasks[1].renderEndTime).toBeNull(); // Fixed end
|
||||
expect(tasks[1].manualEndTime).toBeTruthy();
|
||||
expect(tasks[1].id).toEqual('id2');
|
||||
expect(tasks[1].task).toEqual('test2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as d3 from 'd3'
|
||||
import * as d3 from 'd3';
|
||||
|
||||
import { parser } from './parser/gantt'
|
||||
import ganttDb from './ganttDb'
|
||||
import { parser } from './parser/gantt';
|
||||
import ganttDb from './ganttDb';
|
||||
|
||||
parser.yy = ganttDb
|
||||
parser.yy = ganttDb;
|
||||
|
||||
const conf = {
|
||||
titleTopMargin: 25,
|
||||
@ -15,287 +15,316 @@ const conf = {
|
||||
gridLineStartPadding: 35,
|
||||
fontSize: 11,
|
||||
fontFamily: '"Open-Sans", "sans-serif"'
|
||||
}
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf)
|
||||
};
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
keys.forEach(function (key) {
|
||||
conf[key] = cnf[key]
|
||||
})
|
||||
}
|
||||
let w
|
||||
export const draw = function (text, id) {
|
||||
parser.yy.clear()
|
||||
parser.parse(text)
|
||||
keys.forEach(function(key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
let w;
|
||||
export const draw = function(text, id) {
|
||||
parser.yy.clear();
|
||||
parser.parse(text);
|
||||
|
||||
const elem = document.getElementById(id)
|
||||
w = elem.parentElement.offsetWidth
|
||||
const elem = document.getElementById(id);
|
||||
w = elem.parentElement.offsetWidth;
|
||||
|
||||
if (typeof w === 'undefined') {
|
||||
w = 1200
|
||||
w = 1200;
|
||||
}
|
||||
|
||||
if (typeof conf.useWidth !== 'undefined') {
|
||||
w = conf.useWidth
|
||||
w = conf.useWidth;
|
||||
}
|
||||
|
||||
const taskArray = parser.yy.getTasks()
|
||||
const taskArray = parser.yy.getTasks();
|
||||
|
||||
// Set height based on number of tasks
|
||||
const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding
|
||||
const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding;
|
||||
|
||||
elem.setAttribute('height', '100%')
|
||||
elem.setAttribute('height', '100%');
|
||||
// Set viewBox
|
||||
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h)
|
||||
const svg = d3.select(`[id="${id}"]`)
|
||||
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
|
||||
const svg = d3.select(`[id="${id}"]`);
|
||||
|
||||
// Set timescale
|
||||
const timeScale = d3.scaleTime()
|
||||
.domain([d3.min(taskArray, function (d) {
|
||||
return d.startTime
|
||||
}),
|
||||
d3.max(taskArray, function (d) {
|
||||
return d.endTime
|
||||
})])
|
||||
.rangeRound([0, w - conf.leftPadding - conf.rightPadding])
|
||||
const timeScale = d3
|
||||
.scaleTime()
|
||||
.domain([
|
||||
d3.min(taskArray, function(d) {
|
||||
return d.startTime;
|
||||
}),
|
||||
d3.max(taskArray, function(d) {
|
||||
return d.endTime;
|
||||
})
|
||||
])
|
||||
.rangeRound([0, w - conf.leftPadding - conf.rightPadding]);
|
||||
|
||||
let categories = []
|
||||
let categories = [];
|
||||
|
||||
for (let i = 0; i < taskArray.length; i++) {
|
||||
categories.push(taskArray[i].type)
|
||||
categories.push(taskArray[i].type);
|
||||
}
|
||||
|
||||
const catsUnfiltered = categories // for vert labels
|
||||
const catsUnfiltered = categories; // for vert labels
|
||||
|
||||
categories = checkUnique(categories)
|
||||
categories = checkUnique(categories);
|
||||
|
||||
makeGant(taskArray, w, h)
|
||||
makeGant(taskArray, w, h);
|
||||
if (typeof conf.useWidth !== 'undefined') {
|
||||
elem.setAttribute('width', w)
|
||||
elem.setAttribute('width', w);
|
||||
}
|
||||
|
||||
svg.append('text')
|
||||
svg
|
||||
.append('text')
|
||||
.text(parser.yy.getTitle())
|
||||
.attr('x', w / 2)
|
||||
.attr('y', conf.titleTopMargin)
|
||||
.attr('class', 'titleText')
|
||||
.attr('class', 'titleText');
|
||||
|
||||
function makeGant (tasks, pageWidth, pageHeight) {
|
||||
const barHeight = conf.barHeight
|
||||
const gap = barHeight + conf.barGap
|
||||
const topPadding = conf.topPadding
|
||||
const leftPadding = conf.leftPadding
|
||||
function makeGant(tasks, pageWidth, pageHeight) {
|
||||
const barHeight = conf.barHeight;
|
||||
const gap = barHeight + conf.barGap;
|
||||
const topPadding = conf.topPadding;
|
||||
const leftPadding = conf.leftPadding;
|
||||
|
||||
const colorScale = d3.scaleLinear()
|
||||
const colorScale = d3
|
||||
.scaleLinear()
|
||||
.domain([0, categories.length])
|
||||
.range(['#00B9FA', '#F95002'])
|
||||
.interpolate(d3.interpolateHcl)
|
||||
.interpolate(d3.interpolateHcl);
|
||||
|
||||
makeGrid(leftPadding, topPadding, pageWidth, pageHeight)
|
||||
drawRects(tasks, gap, topPadding, leftPadding, barHeight, colorScale, pageWidth, pageHeight)
|
||||
vertLabels(gap, topPadding, leftPadding, barHeight, colorScale)
|
||||
drawToday(leftPadding, topPadding, pageWidth, pageHeight)
|
||||
makeGrid(leftPadding, topPadding, pageWidth, pageHeight);
|
||||
drawRects(tasks, gap, topPadding, leftPadding, barHeight, colorScale, pageWidth, pageHeight);
|
||||
vertLabels(gap, topPadding, leftPadding, barHeight, colorScale);
|
||||
drawToday(leftPadding, topPadding, pageWidth, pageHeight);
|
||||
}
|
||||
|
||||
function drawRects (theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
|
||||
function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w, h) {
|
||||
// Draw background rects covering the entire width of the graph, these form the section rows.
|
||||
svg.append('g')
|
||||
svg
|
||||
.append('g')
|
||||
.selectAll('rect')
|
||||
.data(theArray)
|
||||
.enter()
|
||||
.append('rect')
|
||||
.attr('x', 0)
|
||||
.attr('y', function (d, i) {
|
||||
return i * theGap + theTopPad - 2
|
||||
.attr('y', function(d, i) {
|
||||
return i * theGap + theTopPad - 2;
|
||||
})
|
||||
.attr('width', function () {
|
||||
return w - conf.rightPadding / 2
|
||||
.attr('width', function() {
|
||||
return w - conf.rightPadding / 2;
|
||||
})
|
||||
.attr('height', theGap)
|
||||
.attr('class', function (d) {
|
||||
.attr('class', function(d) {
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
if (d.type === categories[i]) {
|
||||
return 'section section' + (i % conf.numberSectionStyles)
|
||||
return 'section section' + (i % conf.numberSectionStyles);
|
||||
}
|
||||
}
|
||||
return 'section section0'
|
||||
})
|
||||
return 'section section0';
|
||||
});
|
||||
|
||||
// Draw the rects representing the tasks
|
||||
const rectangles = svg.append('g')
|
||||
const rectangles = svg
|
||||
.append('g')
|
||||
.selectAll('rect')
|
||||
.data(theArray)
|
||||
.enter()
|
||||
.enter();
|
||||
|
||||
rectangles.append('rect')
|
||||
.attr('id', function (d) { return d.id })
|
||||
rectangles
|
||||
.append('rect')
|
||||
.attr('id', function(d) {
|
||||
return d.id;
|
||||
})
|
||||
.attr('rx', 3)
|
||||
.attr('ry', 3)
|
||||
.attr('x', function (d) {
|
||||
.attr('x', function(d) {
|
||||
if (d.milestone) {
|
||||
return timeScale(d.startTime) + theSidePad + (0.5 * (timeScale(d.endTime) - timeScale(d.startTime))) - (0.5 * theBarHeight)
|
||||
return (
|
||||
timeScale(d.startTime) +
|
||||
theSidePad +
|
||||
0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) -
|
||||
0.5 * theBarHeight
|
||||
);
|
||||
}
|
||||
return timeScale(d.startTime) + theSidePad
|
||||
return timeScale(d.startTime) + theSidePad;
|
||||
})
|
||||
.attr('y', function (d, i) {
|
||||
return i * theGap + theTopPad
|
||||
.attr('y', function(d, i) {
|
||||
return i * theGap + theTopPad;
|
||||
})
|
||||
.attr('width', function (d) {
|
||||
.attr('width', function(d) {
|
||||
if (d.milestone) {
|
||||
return theBarHeight
|
||||
return theBarHeight;
|
||||
}
|
||||
return (timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime))
|
||||
return timeScale(d.renderEndTime || d.endTime) - timeScale(d.startTime);
|
||||
})
|
||||
.attr('height', theBarHeight)
|
||||
.attr('transform-origin', function (d, i) {
|
||||
return (timeScale(d.startTime) + theSidePad + 0.5 * (timeScale(d.endTime) - timeScale(d.startTime))).toString() + 'px ' + (i * theGap + theTopPad + 0.5 * theBarHeight).toString() + 'px'
|
||||
.attr('transform-origin', function(d, i) {
|
||||
return (
|
||||
(
|
||||
timeScale(d.startTime) +
|
||||
theSidePad +
|
||||
0.5 * (timeScale(d.endTime) - timeScale(d.startTime))
|
||||
).toString() +
|
||||
'px ' +
|
||||
(i * theGap + theTopPad + 0.5 * theBarHeight).toString() +
|
||||
'px'
|
||||
);
|
||||
})
|
||||
.attr('class', function (d) {
|
||||
const res = 'task'
|
||||
.attr('class', function(d) {
|
||||
const res = 'task';
|
||||
|
||||
let classStr = ''
|
||||
let classStr = '';
|
||||
if (d.classes.length > 0) {
|
||||
classStr = d.classes.join(' ')
|
||||
classStr = d.classes.join(' ');
|
||||
}
|
||||
|
||||
let secNum = 0
|
||||
let secNum = 0;
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
if (d.type === categories[i]) {
|
||||
secNum = (i % conf.numberSectionStyles)
|
||||
secNum = i % conf.numberSectionStyles;
|
||||
}
|
||||
}
|
||||
|
||||
let taskClass = ''
|
||||
let taskClass = '';
|
||||
if (d.active) {
|
||||
if (d.crit) {
|
||||
taskClass += ' activeCrit'
|
||||
taskClass += ' activeCrit';
|
||||
} else {
|
||||
taskClass = ' active'
|
||||
taskClass = ' active';
|
||||
}
|
||||
} else if (d.done) {
|
||||
if (d.crit) {
|
||||
taskClass = ' doneCrit'
|
||||
taskClass = ' doneCrit';
|
||||
} else {
|
||||
taskClass = ' done'
|
||||
taskClass = ' done';
|
||||
}
|
||||
} else {
|
||||
if (d.crit) {
|
||||
taskClass += ' crit'
|
||||
taskClass += ' crit';
|
||||
}
|
||||
}
|
||||
|
||||
if (taskClass.length === 0) {
|
||||
taskClass = ' task'
|
||||
taskClass = ' task';
|
||||
}
|
||||
|
||||
if (d.milestone) {
|
||||
taskClass = ' milestone ' + taskClass
|
||||
taskClass = ' milestone ' + taskClass;
|
||||
}
|
||||
|
||||
taskClass += secNum
|
||||
taskClass += secNum;
|
||||
|
||||
taskClass += ' ' + classStr
|
||||
taskClass += ' ' + classStr;
|
||||
|
||||
return res + taskClass
|
||||
})
|
||||
return res + taskClass;
|
||||
});
|
||||
|
||||
// Append task labels
|
||||
rectangles.append('text')
|
||||
.attr('id', function (d) { return d.id + '-text' })
|
||||
.text(function (d) {
|
||||
return d.task
|
||||
rectangles
|
||||
.append('text')
|
||||
.attr('id', function(d) {
|
||||
return d.id + '-text';
|
||||
})
|
||||
.text(function(d) {
|
||||
return d.task;
|
||||
})
|
||||
.attr('font-size', conf.fontSize)
|
||||
.attr('x', function (d) {
|
||||
let startX = timeScale(d.startTime)
|
||||
let endX = timeScale(d.renderEndTime || d.endTime)
|
||||
.attr('x', function(d) {
|
||||
let startX = timeScale(d.startTime);
|
||||
let endX = timeScale(d.renderEndTime || d.endTime);
|
||||
if (d.milestone) {
|
||||
startX += (0.5 * (timeScale(d.endTime) - timeScale(d.startTime))) - (0.5 * theBarHeight)
|
||||
startX += 0.5 * (timeScale(d.endTime) - timeScale(d.startTime)) - 0.5 * theBarHeight;
|
||||
}
|
||||
if (d.milestone) {
|
||||
endX = startX + theBarHeight
|
||||
endX = startX + theBarHeight;
|
||||
}
|
||||
const textWidth = this.getBBox().width
|
||||
const textWidth = this.getBBox().width;
|
||||
|
||||
// Check id text width > width of rectangle
|
||||
if (textWidth > (endX - startX)) {
|
||||
if (textWidth > endX - startX) {
|
||||
if (endX + textWidth + 1.5 * conf.leftPadding > w) {
|
||||
return startX + theSidePad - 5
|
||||
return startX + theSidePad - 5;
|
||||
} else {
|
||||
return endX + theSidePad + 5
|
||||
return endX + theSidePad + 5;
|
||||
}
|
||||
} else {
|
||||
return (endX - startX) / 2 + startX + theSidePad
|
||||
return (endX - startX) / 2 + startX + theSidePad;
|
||||
}
|
||||
})
|
||||
.attr('y', function (d, i) {
|
||||
return i * theGap + (conf.barHeight / 2) + (conf.fontSize / 2 - 2) + theTopPad
|
||||
.attr('y', function(d, i) {
|
||||
return i * theGap + conf.barHeight / 2 + (conf.fontSize / 2 - 2) + theTopPad;
|
||||
})
|
||||
.attr('text-height', theBarHeight)
|
||||
.attr('class', function (d) {
|
||||
const startX = timeScale(d.startTime)
|
||||
let endX = timeScale(d.endTime)
|
||||
.attr('class', function(d) {
|
||||
const startX = timeScale(d.startTime);
|
||||
let endX = timeScale(d.endTime);
|
||||
if (d.milestone) {
|
||||
endX = startX + theBarHeight
|
||||
endX = startX + theBarHeight;
|
||||
}
|
||||
const textWidth = this.getBBox().width
|
||||
const textWidth = this.getBBox().width;
|
||||
|
||||
let classStr = ''
|
||||
let classStr = '';
|
||||
if (d.classes.length > 0) {
|
||||
classStr = d.classes.join(' ')
|
||||
classStr = d.classes.join(' ');
|
||||
}
|
||||
|
||||
let secNum = 0
|
||||
let secNum = 0;
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
if (d.type === categories[i]) {
|
||||
secNum = (i % conf.numberSectionStyles)
|
||||
secNum = i % conf.numberSectionStyles;
|
||||
}
|
||||
}
|
||||
|
||||
let taskType = ''
|
||||
let taskType = '';
|
||||
if (d.active) {
|
||||
if (d.crit) {
|
||||
taskType = 'activeCritText' + secNum
|
||||
taskType = 'activeCritText' + secNum;
|
||||
} else {
|
||||
taskType = 'activeText' + secNum
|
||||
taskType = 'activeText' + secNum;
|
||||
}
|
||||
}
|
||||
|
||||
if (d.done) {
|
||||
if (d.crit) {
|
||||
taskType = taskType + ' doneCritText' + secNum
|
||||
taskType = taskType + ' doneCritText' + secNum;
|
||||
} else {
|
||||
taskType = taskType + ' doneText' + secNum
|
||||
taskType = taskType + ' doneText' + secNum;
|
||||
}
|
||||
} else {
|
||||
if (d.crit) {
|
||||
taskType = taskType + ' critText' + secNum
|
||||
taskType = taskType + ' critText' + secNum;
|
||||
}
|
||||
}
|
||||
|
||||
if (d.milestone) {
|
||||
taskType += ' milestoneText'
|
||||
taskType += ' milestoneText';
|
||||
}
|
||||
|
||||
// Check id text width > width of rectangle
|
||||
if (textWidth > (endX - startX)) {
|
||||
if (textWidth > endX - startX) {
|
||||
if (endX + textWidth + 1.5 * conf.leftPadding > w) {
|
||||
return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType
|
||||
return classStr + ' taskTextOutsideLeft taskTextOutside' + secNum + ' ' + taskType;
|
||||
} else {
|
||||
return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType
|
||||
return classStr + ' taskTextOutsideRight taskTextOutside' + secNum + ' ' + taskType;
|
||||
}
|
||||
} else {
|
||||
return classStr + ' taskText taskText' + secNum + ' ' + taskType
|
||||
return classStr + ' taskText taskText' + secNum + ' ' + taskType;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function makeGrid (theSidePad, theTopPad, w, h) {
|
||||
let xAxis = d3.axisBottom(timeScale)
|
||||
function makeGrid(theSidePad, theTopPad, w, h) {
|
||||
let xAxis = d3
|
||||
.axisBottom(timeScale)
|
||||
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
|
||||
.tickFormat(d3.timeFormat(parser.yy.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'))
|
||||
.tickFormat(d3.timeFormat(parser.yy.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'));
|
||||
|
||||
svg.append('g')
|
||||
svg
|
||||
.append('g')
|
||||
.attr('class', 'grid')
|
||||
.attr('transform', 'translate(' + theSidePad + ', ' + (h - 50) + ')')
|
||||
.call(xAxis)
|
||||
@ -304,90 +333,92 @@ export const draw = function (text, id) {
|
||||
.attr('fill', '#000')
|
||||
.attr('stroke', 'none')
|
||||
.attr('font-size', 10)
|
||||
.attr('dy', '1em')
|
||||
.attr('dy', '1em');
|
||||
}
|
||||
|
||||
function vertLabels (theGap, theTopPad) {
|
||||
const numOccurances = []
|
||||
let prevGap = 0
|
||||
function vertLabels(theGap, theTopPad) {
|
||||
const numOccurances = [];
|
||||
let prevGap = 0;
|
||||
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)]
|
||||
numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)];
|
||||
}
|
||||
|
||||
svg.append('g') // without doing this, impossible to put grid lines behind text
|
||||
svg
|
||||
.append('g') // without doing this, impossible to put grid lines behind text
|
||||
.selectAll('text')
|
||||
.data(numOccurances)
|
||||
.enter()
|
||||
.append('text')
|
||||
.text(function (d) {
|
||||
return d[0]
|
||||
.text(function(d) {
|
||||
return d[0];
|
||||
})
|
||||
.attr('x', 10)
|
||||
.attr('y', function (d, i) {
|
||||
.attr('y', function(d, i) {
|
||||
if (i > 0) {
|
||||
for (let j = 0; j < i; j++) {
|
||||
prevGap += numOccurances[i - 1][1]
|
||||
return d[1] * theGap / 2 + prevGap * theGap + theTopPad
|
||||
prevGap += numOccurances[i - 1][1];
|
||||
return (d[1] * theGap) / 2 + prevGap * theGap + theTopPad;
|
||||
}
|
||||
} else {
|
||||
return d[1] * theGap / 2 + theTopPad
|
||||
return (d[1] * theGap) / 2 + theTopPad;
|
||||
}
|
||||
})
|
||||
.attr('class', function (d) {
|
||||
.attr('class', function(d) {
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
if (d[0] === categories[i]) {
|
||||
return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles)
|
||||
return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles);
|
||||
}
|
||||
}
|
||||
return 'sectionTitle'
|
||||
})
|
||||
return 'sectionTitle';
|
||||
});
|
||||
}
|
||||
|
||||
function drawToday (theSidePad, theTopPad, w, h) {
|
||||
const todayG = svg.append('g')
|
||||
.attr('class', 'today')
|
||||
function drawToday(theSidePad, theTopPad, w, h) {
|
||||
const todayG = svg.append('g').attr('class', 'today');
|
||||
|
||||
const today = new Date()
|
||||
const today = new Date();
|
||||
|
||||
todayG.append('line')
|
||||
todayG
|
||||
.append('line')
|
||||
.attr('x1', timeScale(today) + theSidePad)
|
||||
.attr('x2', timeScale(today) + theSidePad)
|
||||
.attr('y1', conf.titleTopMargin)
|
||||
.attr('y2', h - conf.titleTopMargin)
|
||||
.attr('class', 'today')
|
||||
.attr('class', 'today');
|
||||
}
|
||||
|
||||
// from this stackexchange question: http://stackoverflow.com/questions/1890203/unique-for-arrays-in-javascript
|
||||
function checkUnique (arr) {
|
||||
const hash = {}
|
||||
const result = []
|
||||
function checkUnique(arr) {
|
||||
const hash = {};
|
||||
const result = [];
|
||||
for (let i = 0, l = arr.length; i < l; ++i) {
|
||||
if (!hash.hasOwnProperty(arr[i])) { // it works with objects! in FF, at least
|
||||
hash[arr[i]] = true
|
||||
result.push(arr[i])
|
||||
if (!hash.hasOwnProperty(arr[i])) {
|
||||
// it works with objects! in FF, at least
|
||||
hash[arr[i]] = true;
|
||||
result.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
// from this stackexchange question: http://stackoverflow.com/questions/14227981/count-how-many-strings-in-an-array-have-duplicates-in-the-same-array
|
||||
function getCounts (arr) {
|
||||
let i = arr.length // const to loop over
|
||||
const obj = {} // obj to store results
|
||||
function getCounts(arr) {
|
||||
let i = arr.length; // const to loop over
|
||||
const obj = {}; // obj to store results
|
||||
while (i) {
|
||||
obj[arr[--i]] = (obj[arr[i]] || 0) + 1 // count occurrences
|
||||
obj[arr[--i]] = (obj[arr[i]] || 0) + 1; // count occurrences
|
||||
}
|
||||
return obj
|
||||
return obj;
|
||||
}
|
||||
|
||||
// get specific from everything
|
||||
function getCount (word, arr) {
|
||||
return getCounts(arr)[word] || 0
|
||||
function getCount(word, arr) {
|
||||
return getCounts(arr)[word] || 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw
|
||||
}
|
||||
};
|
||||
|
@ -1,50 +1,52 @@
|
||||
/* eslint-env jasmine */
|
||||
/* eslint-disable no-eval */
|
||||
import { parser } from './gantt'
|
||||
import ganttDb from '../ganttDb'
|
||||
import { parser } from './gantt';
|
||||
import ganttDb from '../ganttDb';
|
||||
|
||||
const parserFnConstructor = (str) => {
|
||||
const parserFnConstructor = str => {
|
||||
return () => {
|
||||
parser.parse(str)
|
||||
}
|
||||
}
|
||||
parser.parse(str);
|
||||
};
|
||||
};
|
||||
|
||||
describe('when parsing a gantt diagram it', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = ganttDb
|
||||
parser.yy.clear()
|
||||
})
|
||||
describe('when parsing a gantt diagram it', function() {
|
||||
beforeEach(function() {
|
||||
parser.yy = ganttDb;
|
||||
parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should handle a dateFormat definition', function () {
|
||||
const str = 'gantt\ndateFormat yyyy-mm-dd'
|
||||
it('should handle a dateFormat definition', function() {
|
||||
const str = 'gantt\ndateFormat yyyy-mm-dd';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow()
|
||||
})
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle a inclusive end date definition', function () {
|
||||
const str = 'gantt\ndateFormat yyyy-mm-dd\ninclusiveEndDates'
|
||||
it('should handle a inclusive end date definition', function() {
|
||||
const str = 'gantt\ndateFormat yyyy-mm-dd\ninclusiveEndDates';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow()
|
||||
})
|
||||
it('should handle a title definition', function () {
|
||||
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid'
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
});
|
||||
it('should handle a title definition', function() {
|
||||
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow()
|
||||
})
|
||||
it('should handle an excludes definition', function () {
|
||||
const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid\nexcludes weekdays 2019-02-01'
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
});
|
||||
it('should handle an excludes definition', function() {
|
||||
const str =
|
||||
'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid\nexcludes weekdays 2019-02-01';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow()
|
||||
})
|
||||
it('should handle a section definition', function () {
|
||||
const str = 'gantt\n' +
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
});
|
||||
it('should handle a section definition', function() {
|
||||
const str =
|
||||
'gantt\n' +
|
||||
'dateFormat yyyy-mm-dd\n' +
|
||||
'title Adding gantt diagram functionality to mermaid\n' +
|
||||
'excludes weekdays 2019-02-01\n' +
|
||||
'section Documentation'
|
||||
'section Documentation';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow()
|
||||
})
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
});
|
||||
/**
|
||||
* Beslutsflöde inligt nedan. Obs bla bla bla
|
||||
* ```
|
||||
@ -56,22 +58,23 @@ describe('when parsing a gantt diagram it', function () {
|
||||
```
|
||||
* params bapa - a unique bapap
|
||||
*/
|
||||
it('should handle a task definition', function () {
|
||||
const str = 'gantt\n' +
|
||||
it('should handle a task definition', function() {
|
||||
const str =
|
||||
'gantt\n' +
|
||||
'dateFormat YYYY-MM-DD\n' +
|
||||
'title Adding gantt diagram functionality to mermaid\n' +
|
||||
'section Documentation\n' +
|
||||
'Design jison grammar:des1, 2014-01-01, 2014-01-04'
|
||||
'Design jison grammar:des1, 2014-01-01, 2014-01-04';
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow()
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
|
||||
const tasks = parser.yy.getTasks()
|
||||
const tasks = parser.yy.getTasks();
|
||||
|
||||
expect(tasks[0].startTime).toEqual(new Date(2014, 0, 1))
|
||||
expect(tasks[0].endTime).toEqual(new Date(2014, 0, 4))
|
||||
expect(tasks[0].id).toEqual('des1')
|
||||
expect(tasks[0].task).toEqual('Design jison grammar')
|
||||
})
|
||||
expect(tasks[0].startTime).toEqual(new Date(2014, 0, 1));
|
||||
expect(tasks[0].endTime).toEqual(new Date(2014, 0, 4));
|
||||
expect(tasks[0].id).toEqual('des1');
|
||||
expect(tasks[0].task).toEqual('Design jison grammar');
|
||||
});
|
||||
it.each`
|
||||
tags | milestone | done | crit | active
|
||||
${'milestone'} | ${true} | ${false} | ${false} | ${false}
|
||||
@ -79,25 +82,28 @@ describe('when parsing a gantt diagram it', function () {
|
||||
${'crit'} | ${false} | ${false} | ${true} | ${false}
|
||||
${'active'} | ${false} | ${false} | ${false} | ${true}
|
||||
${'crit,milestone,done'} | ${true} | ${true} | ${true} | ${false}
|
||||
`('should handle a task with tags $tags', ({ tags, milestone, done, crit, active }) => {
|
||||
const str = 'gantt\n' +
|
||||
'dateFormat YYYY-MM-DD\n' +
|
||||
'title Adding gantt diagram functionality to mermaid\n' +
|
||||
'section Documentation\n' +
|
||||
'test task:' + tags + ', 2014-01-01, 2014-01-04'
|
||||
`('should handle a task with tags $tags', ({ tags, milestone, done, crit, active }) => {
|
||||
const str =
|
||||
'gantt\n' +
|
||||
'dateFormat YYYY-MM-DD\n' +
|
||||
'title Adding gantt diagram functionality to mermaid\n' +
|
||||
'section Documentation\n' +
|
||||
'test task:' +
|
||||
tags +
|
||||
', 2014-01-01, 2014-01-04';
|
||||
|
||||
const allowedTags = ['active', 'done', 'crit', 'milestone']
|
||||
const allowedTags = ['active', 'done', 'crit', 'milestone'];
|
||||
|
||||
expect(parserFnConstructor(str)).not.toThrow()
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
|
||||
const tasks = parser.yy.getTasks()
|
||||
const tasks = parser.yy.getTasks();
|
||||
|
||||
allowedTags.forEach(function (t) {
|
||||
if (eval(t)) {
|
||||
expect(tasks[0][t]).toBeTruthy()
|
||||
} else {
|
||||
expect(tasks[0][t]).toBeFalsy()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
allowedTags.forEach(function(t) {
|
||||
if (eval(t)) {
|
||||
expect(tasks[0][t]).toBeTruthy();
|
||||
} else {
|
||||
expect(tasks[0][t]).toBeFalsy();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,98 +1,100 @@
|
||||
import _ from 'lodash'
|
||||
import _ from 'lodash';
|
||||
|
||||
import { logger } from '../../logger'
|
||||
import { logger } from '../../logger';
|
||||
|
||||
let commits = {}
|
||||
let head = null
|
||||
let branches = { 'master': head }
|
||||
let curBranch = 'master'
|
||||
let direction = 'LR'
|
||||
let seq = 0
|
||||
let commits = {};
|
||||
let head = null;
|
||||
let branches = { master: head };
|
||||
let curBranch = 'master';
|
||||
let direction = 'LR';
|
||||
let seq = 0;
|
||||
|
||||
function getRandomInt (min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min
|
||||
function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
function getId () {
|
||||
const pool = '0123456789abcdef'
|
||||
let id = ''
|
||||
function getId() {
|
||||
const pool = '0123456789abcdef';
|
||||
let id = '';
|
||||
for (let i = 0; i < 7; i++) {
|
||||
id += pool[getRandomInt(0, 16)]
|
||||
id += pool[getRandomInt(0, 16)];
|
||||
}
|
||||
return id
|
||||
return id;
|
||||
}
|
||||
|
||||
function isfastforwardable (currentCommit, otherCommit) {
|
||||
logger.debug('Entering isfastforwardable:', currentCommit.id, otherCommit.id)
|
||||
function isfastforwardable(currentCommit, otherCommit) {
|
||||
logger.debug('Entering isfastforwardable:', currentCommit.id, otherCommit.id);
|
||||
while (currentCommit.seq <= otherCommit.seq && currentCommit !== otherCommit) {
|
||||
// only if other branch has more commits
|
||||
if (otherCommit.parent == null) break
|
||||
if (otherCommit.parent == null) break;
|
||||
if (Array.isArray(otherCommit.parent)) {
|
||||
logger.debug('In merge commit:', otherCommit.parent)
|
||||
return isfastforwardable(currentCommit, commits[otherCommit.parent[0]]) ||
|
||||
logger.debug('In merge commit:', otherCommit.parent);
|
||||
return (
|
||||
isfastforwardable(currentCommit, commits[otherCommit.parent[0]]) ||
|
||||
isfastforwardable(currentCommit, commits[otherCommit.parent[1]])
|
||||
);
|
||||
} else {
|
||||
otherCommit = commits[otherCommit.parent]
|
||||
otherCommit = commits[otherCommit.parent];
|
||||
}
|
||||
}
|
||||
logger.debug(currentCommit.id, otherCommit.id)
|
||||
return currentCommit.id === otherCommit.id
|
||||
logger.debug(currentCommit.id, otherCommit.id);
|
||||
return currentCommit.id === otherCommit.id;
|
||||
}
|
||||
|
||||
function isReachableFrom (currentCommit, otherCommit) {
|
||||
const currentSeq = currentCommit.seq
|
||||
const otherSeq = otherCommit.seq
|
||||
if (currentSeq > otherSeq) return isfastforwardable(otherCommit, currentCommit)
|
||||
return false
|
||||
function isReachableFrom(currentCommit, otherCommit) {
|
||||
const currentSeq = currentCommit.seq;
|
||||
const otherSeq = otherCommit.seq;
|
||||
if (currentSeq > otherSeq) return isfastforwardable(otherCommit, currentCommit);
|
||||
return false;
|
||||
}
|
||||
|
||||
export const setDirection = function (dir) {
|
||||
direction = dir
|
||||
}
|
||||
let options = {}
|
||||
export const setOptions = function (rawOptString) {
|
||||
logger.debug('options str', rawOptString)
|
||||
rawOptString = rawOptString && rawOptString.trim()
|
||||
rawOptString = rawOptString || '{}'
|
||||
export const setDirection = function(dir) {
|
||||
direction = dir;
|
||||
};
|
||||
let options = {};
|
||||
export const setOptions = function(rawOptString) {
|
||||
logger.debug('options str', rawOptString);
|
||||
rawOptString = rawOptString && rawOptString.trim();
|
||||
rawOptString = rawOptString || '{}';
|
||||
try {
|
||||
options = JSON.parse(rawOptString)
|
||||
options = JSON.parse(rawOptString);
|
||||
} catch (e) {
|
||||
logger.error('error while parsing gitGraph options', e.message)
|
||||
logger.error('error while parsing gitGraph options', e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getOptions = function () {
|
||||
return options
|
||||
}
|
||||
export const getOptions = function() {
|
||||
return options;
|
||||
};
|
||||
|
||||
export const commit = function (msg) {
|
||||
export const commit = function(msg) {
|
||||
const commit = {
|
||||
id: getId(),
|
||||
message: msg,
|
||||
seq: seq++,
|
||||
parent: head == null ? null : head.id
|
||||
}
|
||||
head = commit
|
||||
commits[commit.id] = commit
|
||||
branches[curBranch] = commit.id
|
||||
logger.debug('in pushCommit ' + commit.id)
|
||||
}
|
||||
};
|
||||
head = commit;
|
||||
commits[commit.id] = commit;
|
||||
branches[curBranch] = commit.id;
|
||||
logger.debug('in pushCommit ' + commit.id);
|
||||
};
|
||||
|
||||
export const branch = function (name) {
|
||||
branches[name] = head != null ? head.id : null
|
||||
logger.debug('in createBranch')
|
||||
}
|
||||
export const branch = function(name) {
|
||||
branches[name] = head != null ? head.id : null;
|
||||
logger.debug('in createBranch');
|
||||
};
|
||||
|
||||
export const merge = function (otherBranch) {
|
||||
const currentCommit = commits[branches[curBranch]]
|
||||
const otherCommit = commits[branches[otherBranch]]
|
||||
export const merge = function(otherBranch) {
|
||||
const currentCommit = commits[branches[curBranch]];
|
||||
const otherCommit = commits[branches[otherBranch]];
|
||||
if (isReachableFrom(currentCommit, otherCommit)) {
|
||||
logger.debug('Already merged')
|
||||
return
|
||||
logger.debug('Already merged');
|
||||
return;
|
||||
}
|
||||
if (isfastforwardable(currentCommit, otherCommit)) {
|
||||
branches[curBranch] = branches[otherBranch]
|
||||
head = commits[branches[curBranch]]
|
||||
branches[curBranch] = branches[otherBranch];
|
||||
head = commits[branches[curBranch]];
|
||||
} else {
|
||||
// create merge commit
|
||||
const commit = {
|
||||
@ -100,113 +102,125 @@ export const merge = function (otherBranch) {
|
||||
message: 'merged branch ' + otherBranch + ' into ' + curBranch,
|
||||
seq: seq++,
|
||||
parent: [head == null ? null : head.id, branches[otherBranch]]
|
||||
}
|
||||
head = commit
|
||||
commits[commit.id] = commit
|
||||
branches[curBranch] = commit.id
|
||||
};
|
||||
head = commit;
|
||||
commits[commit.id] = commit;
|
||||
branches[curBranch] = commit.id;
|
||||
}
|
||||
logger.debug(branches)
|
||||
logger.debug('in mergeBranch')
|
||||
}
|
||||
logger.debug(branches);
|
||||
logger.debug('in mergeBranch');
|
||||
};
|
||||
|
||||
export const checkout = function (branch) {
|
||||
logger.debug('in checkout')
|
||||
curBranch = branch
|
||||
const id = branches[curBranch]
|
||||
head = commits[id]
|
||||
}
|
||||
export const checkout = function(branch) {
|
||||
logger.debug('in checkout');
|
||||
curBranch = branch;
|
||||
const id = branches[curBranch];
|
||||
head = commits[id];
|
||||
};
|
||||
|
||||
export const reset = function (commitRef) {
|
||||
logger.debug('in reset', commitRef)
|
||||
const ref = commitRef.split(':')[0]
|
||||
let parentCount = parseInt(commitRef.split(':')[1])
|
||||
let commit = ref === 'HEAD' ? head : commits[branches[ref]]
|
||||
logger.debug(commit, parentCount)
|
||||
export const reset = function(commitRef) {
|
||||
logger.debug('in reset', commitRef);
|
||||
const ref = commitRef.split(':')[0];
|
||||
let parentCount = parseInt(commitRef.split(':')[1]);
|
||||
let commit = ref === 'HEAD' ? head : commits[branches[ref]];
|
||||
logger.debug(commit, parentCount);
|
||||
while (parentCount > 0) {
|
||||
commit = commits[commit.parent]
|
||||
parentCount--
|
||||
commit = commits[commit.parent];
|
||||
parentCount--;
|
||||
if (!commit) {
|
||||
const err = 'Critical error - unique parent commit not found during reset'
|
||||
logger.error(err)
|
||||
throw err
|
||||
const err = 'Critical error - unique parent commit not found during reset';
|
||||
logger.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
head = commit
|
||||
branches[curBranch] = commit.id
|
||||
}
|
||||
head = commit;
|
||||
branches[curBranch] = commit.id;
|
||||
};
|
||||
|
||||
function upsert (arr, key, newval) {
|
||||
const index = arr.indexOf(key)
|
||||
function upsert(arr, key, newval) {
|
||||
const index = arr.indexOf(key);
|
||||
if (index === -1) {
|
||||
arr.push(newval)
|
||||
arr.push(newval);
|
||||
} else {
|
||||
arr.splice(index, 1, newval)
|
||||
arr.splice(index, 1, newval);
|
||||
}
|
||||
}
|
||||
|
||||
function prettyPrintCommitHistory (commitArr) {
|
||||
const commit = _.maxBy(commitArr, 'seq')
|
||||
let line = ''
|
||||
commitArr.forEach(function (c) {
|
||||
function prettyPrintCommitHistory(commitArr) {
|
||||
const commit = _.maxBy(commitArr, 'seq');
|
||||
let line = '';
|
||||
commitArr.forEach(function(c) {
|
||||
if (c === commit) {
|
||||
line += '\t*'
|
||||
line += '\t*';
|
||||
} else {
|
||||
line += '\t|'
|
||||
line += '\t|';
|
||||
}
|
||||
})
|
||||
const label = [line, commit.id, commit.seq]
|
||||
});
|
||||
const label = [line, commit.id, commit.seq];
|
||||
for (let branch in branches) {
|
||||
if (branches[branch] === commit.id) label.push(branch)
|
||||
if (branches[branch] === commit.id) label.push(branch);
|
||||
}
|
||||
logger.debug(label.join(' '))
|
||||
logger.debug(label.join(' '));
|
||||
if (Array.isArray(commit.parent)) {
|
||||
const newCommit = commits[commit.parent[0]]
|
||||
upsert(commitArr, commit, newCommit)
|
||||
commitArr.push(commits[commit.parent[1]])
|
||||
const newCommit = commits[commit.parent[0]];
|
||||
upsert(commitArr, commit, newCommit);
|
||||
commitArr.push(commits[commit.parent[1]]);
|
||||
} else if (commit.parent == null) {
|
||||
return
|
||||
return;
|
||||
} else {
|
||||
const nextCommit = commits[commit.parent]
|
||||
upsert(commitArr, commit, nextCommit)
|
||||
const nextCommit = commits[commit.parent];
|
||||
upsert(commitArr, commit, nextCommit);
|
||||
}
|
||||
commitArr = _.uniqBy(commitArr, 'id')
|
||||
prettyPrintCommitHistory(commitArr)
|
||||
commitArr = _.uniqBy(commitArr, 'id');
|
||||
prettyPrintCommitHistory(commitArr);
|
||||
}
|
||||
|
||||
export const prettyPrint = function () {
|
||||
logger.debug(commits)
|
||||
const node = getCommitsArray()[0]
|
||||
prettyPrintCommitHistory([node])
|
||||
}
|
||||
export const prettyPrint = function() {
|
||||
logger.debug(commits);
|
||||
const node = getCommitsArray()[0];
|
||||
prettyPrintCommitHistory([node]);
|
||||
};
|
||||
|
||||
export const clear = function () {
|
||||
commits = {}
|
||||
head = null
|
||||
branches = { 'master': head }
|
||||
curBranch = 'master'
|
||||
seq = 0
|
||||
}
|
||||
export const clear = function() {
|
||||
commits = {};
|
||||
head = null;
|
||||
branches = { master: head };
|
||||
curBranch = 'master';
|
||||
seq = 0;
|
||||
};
|
||||
|
||||
export const getBranchesAsObjArray = function () {
|
||||
const branchArr = []
|
||||
export const getBranchesAsObjArray = function() {
|
||||
const branchArr = [];
|
||||
for (let branch in branches) {
|
||||
branchArr.push({ name: branch, commit: commits[branches[branch]] })
|
||||
branchArr.push({ name: branch, commit: commits[branches[branch]] });
|
||||
}
|
||||
return branchArr
|
||||
}
|
||||
return branchArr;
|
||||
};
|
||||
|
||||
export const getBranches = function () { return branches }
|
||||
export const getCommits = function () { return commits }
|
||||
export const getCommitsArray = function () {
|
||||
const commitArr = Object.keys(commits).map(function (key) {
|
||||
return commits[key]
|
||||
})
|
||||
commitArr.forEach(function (o) { logger.debug(o.id) })
|
||||
return _.orderBy(commitArr, ['seq'], ['desc'])
|
||||
}
|
||||
export const getCurrentBranch = function () { return curBranch }
|
||||
export const getDirection = function () { return direction }
|
||||
export const getHead = function () { return head }
|
||||
export const getBranches = function() {
|
||||
return branches;
|
||||
};
|
||||
export const getCommits = function() {
|
||||
return commits;
|
||||
};
|
||||
export const getCommitsArray = function() {
|
||||
const commitArr = Object.keys(commits).map(function(key) {
|
||||
return commits[key];
|
||||
});
|
||||
commitArr.forEach(function(o) {
|
||||
logger.debug(o.id);
|
||||
});
|
||||
return _.orderBy(commitArr, ['seq'], ['desc']);
|
||||
};
|
||||
export const getCurrentBranch = function() {
|
||||
return curBranch;
|
||||
};
|
||||
export const getDirection = function() {
|
||||
return direction;
|
||||
};
|
||||
export const getHead = function() {
|
||||
return head;
|
||||
};
|
||||
|
||||
export default {
|
||||
setDirection,
|
||||
@ -226,4 +240,4 @@ export default {
|
||||
getCurrentBranch,
|
||||
getDirection,
|
||||
getHead
|
||||
}
|
||||
};
|
||||
|
@ -1,201 +1,186 @@
|
||||
/* eslint-env jasmine */
|
||||
import gitGraphAst from './gitGraphAst'
|
||||
import { parser } from './parser/gitGraph'
|
||||
import gitGraphAst from './gitGraphAst';
|
||||
import { parser } from './parser/gitGraph';
|
||||
|
||||
describe('when parsing a gitGraph', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = gitGraphAst
|
||||
parser.yy.clear()
|
||||
})
|
||||
it('should handle a gitGraph defintion', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
'commit\n'
|
||||
describe('when parsing a gitGraph', function() {
|
||||
beforeEach(function() {
|
||||
parser.yy = gitGraphAst;
|
||||
parser.yy.clear();
|
||||
});
|
||||
it('should handle a gitGraph defintion', function() {
|
||||
const str = 'gitGraph:\n' + 'commit\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(Object.keys(commits).length).toBe(1)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
expect(parser.yy.getDirection()).toBe('LR')
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
|
||||
})
|
||||
expect(Object.keys(commits).length).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle a gitGraph defintion with empty options', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
'options\n' +
|
||||
'end\n' +
|
||||
'commit\n'
|
||||
it('should handle a gitGraph defintion with empty options', function() {
|
||||
const str = 'gitGraph:\n' + 'options\n' + 'end\n' + 'commit\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(parser.yy.getOptions()).toEqual({})
|
||||
expect(Object.keys(commits).length).toBe(1)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
expect(parser.yy.getDirection()).toBe('LR')
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
|
||||
})
|
||||
expect(parser.yy.getOptions()).toEqual({});
|
||||
expect(Object.keys(commits).length).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle a gitGraph defintion with valid options', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
'options\n' +
|
||||
'{"key": "value"}\n' +
|
||||
'end\n' +
|
||||
'commit\n'
|
||||
it('should handle a gitGraph defintion with valid options', function() {
|
||||
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(parser.yy.getOptions()['key']).toBe('value')
|
||||
expect(Object.keys(commits).length).toBe(1)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
expect(parser.yy.getDirection()).toBe('LR')
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
|
||||
})
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(parser.yy.getOptions()['key']).toBe('value');
|
||||
expect(Object.keys(commits).length).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should not fail on a gitGraph with malformed json', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
'options\n' +
|
||||
'{"key": "value"\n' +
|
||||
'end\n' +
|
||||
'commit\n'
|
||||
it('should not fail on a gitGraph with malformed json', function() {
|
||||
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(Object.keys(commits).length).toBe(1)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
expect(parser.yy.getDirection()).toBe('LR')
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
|
||||
})
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(Object.keys(commits).length).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle set direction', function () {
|
||||
const str = 'gitGraph BT:\n' +
|
||||
'commit\n'
|
||||
it('should handle set direction', function() {
|
||||
const str = 'gitGraph BT:\n' + 'commit\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(Object.keys(commits).length).toBe(1)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
expect(parser.yy.getDirection()).toBe('BT')
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1)
|
||||
})
|
||||
expect(Object.keys(commits).length).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
expect(parser.yy.getDirection()).toBe('BT');
|
||||
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should checkout a branch', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
'branch new\n' +
|
||||
'checkout new\n'
|
||||
it('should checkout a branch', function() {
|
||||
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(Object.keys(commits).length).toBe(0)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('new')
|
||||
})
|
||||
expect(Object.keys(commits).length).toBe(0);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('new');
|
||||
});
|
||||
|
||||
it('should add commits to checked out branch', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
'branch new\n' +
|
||||
'checkout new\n' +
|
||||
'commit\n' +
|
||||
'commit\n'
|
||||
it('should add commits to checked out branch', function() {
|
||||
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(Object.keys(commits).length).toBe(2)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('new')
|
||||
const branchCommit = parser.yy.getBranches()['new']
|
||||
expect(branchCommit).not.toBeNull()
|
||||
expect(commits[branchCommit].parent).not.toBeNull()
|
||||
})
|
||||
it('should handle commit with args', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
'commit "a commit"\n'
|
||||
expect(Object.keys(commits).length).toBe(2);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('new');
|
||||
const branchCommit = parser.yy.getBranches()['new'];
|
||||
expect(branchCommit).not.toBeNull();
|
||||
expect(commits[branchCommit].parent).not.toBeNull();
|
||||
});
|
||||
it('should handle commit with args', function() {
|
||||
const str = 'gitGraph:\n' + 'commit "a commit"\n';
|
||||
|
||||
parser.parse(str)
|
||||
const commits = parser.yy.getCommits()
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(Object.keys(commits).length).toBe(1)
|
||||
const key = Object.keys(commits)[0]
|
||||
expect(commits[key].message).toBe('a commit')
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
})
|
||||
expect(Object.keys(commits).length).toBe(1);
|
||||
const key = Object.keys(commits)[0];
|
||||
expect(commits[key].message).toBe('a commit');
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
});
|
||||
|
||||
it('it should reset a branch', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
it('it should reset a branch', function() {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'reset master\n'
|
||||
'reset master\n';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(Object.keys(commits).length).toBe(3)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch')
|
||||
expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master'])
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch'])
|
||||
})
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(Object.keys(commits).length).toBe(3);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']);
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']);
|
||||
});
|
||||
|
||||
it('reset can take an argument', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
it('reset can take an argument', function() {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'reset master^\n'
|
||||
'reset master^\n';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(Object.keys(commits).length).toBe(3)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch')
|
||||
const master = commits[parser.yy.getBranches()['master']]
|
||||
expect(parser.yy.getHead().id).toEqual(master.parent)
|
||||
})
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(Object.keys(commits).length).toBe(3);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
const master = commits[parser.yy.getBranches()['master']];
|
||||
expect(parser.yy.getHead().id).toEqual(master.parent);
|
||||
});
|
||||
|
||||
it('it should handle fast forwardable merges', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
it('it should handle fast forwardable merges', function() {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'checkout master\n' +
|
||||
'merge newbranch\n'
|
||||
'merge newbranch\n';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(Object.keys(commits).length).toBe(3)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master'])
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch'])
|
||||
})
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(Object.keys(commits).length).toBe(3);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']);
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']);
|
||||
});
|
||||
|
||||
it('it should handle cases when merge is a noop', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
it('it should handle cases when merge is a noop', function() {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'merge master\n'
|
||||
'merge master\n';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(Object.keys(commits).length).toBe(3)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch')
|
||||
expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master'])
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch'])
|
||||
})
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(Object.keys(commits).length).toBe(3);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']);
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']);
|
||||
});
|
||||
|
||||
it('it should handle merge with 2 parents', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
it('it should handle merge with 2 parents', function() {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
@ -203,19 +188,20 @@ describe('when parsing a gitGraph', function () {
|
||||
'commit\n' +
|
||||
'checkout master\n' +
|
||||
'commit\n' +
|
||||
'merge newbranch\n'
|
||||
'merge newbranch\n';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(Object.keys(commits).length).toBe(5)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master')
|
||||
expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master'])
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master'])
|
||||
})
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(Object.keys(commits).length).toBe(5);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('master');
|
||||
expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']);
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']);
|
||||
});
|
||||
|
||||
it('it should handle ff merge when history walk has two parents (merge commit)', function () {
|
||||
const str = 'gitGraph:\n' +
|
||||
it('it should handle ff merge when history walk has two parents (merge commit)', function() {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
@ -226,16 +212,16 @@ describe('when parsing a gitGraph', function () {
|
||||
'merge newbranch\n' +
|
||||
'commit\n' +
|
||||
'checkout newbranch\n' +
|
||||
'merge master\n'
|
||||
'merge master\n';
|
||||
|
||||
parser.parse(str)
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits()
|
||||
expect(Object.keys(commits).length).toBe(6)
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch')
|
||||
expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master'])
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master'])
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(Object.keys(commits).length).toBe(6);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']);
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']);
|
||||
|
||||
parser.yy.prettyPrint()
|
||||
})
|
||||
})
|
||||
parser.yy.prettyPrint();
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,13 @@
|
||||
import * as d3 from 'd3'
|
||||
import _ from 'lodash'
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
|
||||
import db from './gitGraphAst'
|
||||
import gitGraphParser from './parser/gitGraph'
|
||||
import { logger } from '../../logger'
|
||||
import { interpolateToCurve } from '../../utils'
|
||||
import db from './gitGraphAst';
|
||||
import gitGraphParser from './parser/gitGraph';
|
||||
import { logger } from '../../logger';
|
||||
import { interpolateToCurve } from '../../utils';
|
||||
|
||||
let allCommitsDict = {}
|
||||
let branchNum
|
||||
let allCommitsDict = {};
|
||||
let branchNum;
|
||||
let config = {
|
||||
nodeSpacing: 150,
|
||||
nodeFillColor: 'yellow',
|
||||
@ -25,13 +25,13 @@ let config = {
|
||||
x: -25,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
let apiConfig = {}
|
||||
export const setConf = function (c) {
|
||||
apiConfig = c
|
||||
}
|
||||
};
|
||||
let apiConfig = {};
|
||||
export const setConf = function(c) {
|
||||
apiConfig = c;
|
||||
};
|
||||
|
||||
function svgCreateDefs (svg) {
|
||||
function svgCreateDefs(svg) {
|
||||
svg
|
||||
.append('defs')
|
||||
.append('g')
|
||||
@ -39,8 +39,9 @@ function svgCreateDefs (svg) {
|
||||
.append('circle')
|
||||
.attr('r', config.nodeRadius)
|
||||
.attr('cx', 0)
|
||||
.attr('cy', 0)
|
||||
svg.select('#def-commit')
|
||||
.attr('cy', 0);
|
||||
svg
|
||||
.select('#def-commit')
|
||||
.append('foreignObject')
|
||||
.attr('width', config.nodeLabel.width)
|
||||
.attr('height', config.nodeLabel.height)
|
||||
@ -49,239 +50,293 @@ function svgCreateDefs (svg) {
|
||||
.attr('class', 'node-label')
|
||||
.attr('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility')
|
||||
.append('p')
|
||||
.html('')
|
||||
.html('');
|
||||
}
|
||||
|
||||
function svgDrawLine (svg, points, colorIdx, interpolate) {
|
||||
const curve = interpolateToCurve(interpolate, d3.curveBasis)
|
||||
const color = config.branchColors[colorIdx % config.branchColors.length]
|
||||
const lineGen = d3.line()
|
||||
.x(function (d) {
|
||||
return Math.round(d.x)
|
||||
function svgDrawLine(svg, points, colorIdx, interpolate) {
|
||||
const curve = interpolateToCurve(interpolate, d3.curveBasis);
|
||||
const color = config.branchColors[colorIdx % config.branchColors.length];
|
||||
const lineGen = d3
|
||||
.line()
|
||||
.x(function(d) {
|
||||
return Math.round(d.x);
|
||||
})
|
||||
.y(function (d) {
|
||||
return Math.round(d.y)
|
||||
.y(function(d) {
|
||||
return Math.round(d.y);
|
||||
})
|
||||
.curve(curve)
|
||||
.curve(curve);
|
||||
|
||||
svg
|
||||
.append('svg:path')
|
||||
.attr('d', lineGen(points))
|
||||
.style('stroke', color)
|
||||
.style('stroke-width', config.lineStrokeWidth)
|
||||
.style('fill', 'none')
|
||||
.style('fill', 'none');
|
||||
}
|
||||
|
||||
// Pass in the element and its pre-transform coords
|
||||
function getElementCoords (element, coords) {
|
||||
coords = coords || element.node().getBBox()
|
||||
const ctm = element.node().getCTM()
|
||||
const xn = ctm.e + coords.x * ctm.a
|
||||
const yn = ctm.f + coords.y * ctm.d
|
||||
function getElementCoords(element, coords) {
|
||||
coords = coords || element.node().getBBox();
|
||||
const ctm = element.node().getCTM();
|
||||
const xn = ctm.e + coords.x * ctm.a;
|
||||
const yn = ctm.f + coords.y * ctm.d;
|
||||
return {
|
||||
left: xn,
|
||||
top: yn,
|
||||
width: coords.width,
|
||||
height: coords.height
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function svgDrawLineForCommits (svg, fromId, toId, direction, color) {
|
||||
logger.debug('svgDrawLineForCommits: ', fromId, toId)
|
||||
const fromBbox = getElementCoords(svg.select('#node-' + fromId + ' circle'))
|
||||
const toBbox = getElementCoords(svg.select('#node-' + toId + ' circle'))
|
||||
function svgDrawLineForCommits(svg, fromId, toId, direction, color) {
|
||||
logger.debug('svgDrawLineForCommits: ', fromId, toId);
|
||||
const fromBbox = getElementCoords(svg.select('#node-' + fromId + ' circle'));
|
||||
const toBbox = getElementCoords(svg.select('#node-' + toId + ' circle'));
|
||||
switch (direction) {
|
||||
case 'LR':
|
||||
// (toBbox)
|
||||
// +--------
|
||||
// + (fromBbox)
|
||||
if (fromBbox.left - toBbox.left > config.nodeSpacing) {
|
||||
const lineStart = { x: fromBbox.left - config.nodeSpacing, y: toBbox.top + toBbox.height / 2 }
|
||||
const lineEnd = { x: toBbox.left + toBbox.width, y: toBbox.top + toBbox.height / 2 }
|
||||
svgDrawLine(svg, [lineStart, lineEnd], color, 'linear')
|
||||
svgDrawLine(svg, [
|
||||
{ x: fromBbox.left, y: fromBbox.top + fromBbox.height / 2 },
|
||||
{ x: fromBbox.left - config.nodeSpacing / 2, y: fromBbox.top + fromBbox.height / 2 },
|
||||
{ x: fromBbox.left - config.nodeSpacing / 2, y: lineStart.y },
|
||||
lineStart], color)
|
||||
const lineStart = {
|
||||
x: fromBbox.left - config.nodeSpacing,
|
||||
y: toBbox.top + toBbox.height / 2
|
||||
};
|
||||
const lineEnd = { x: toBbox.left + toBbox.width, y: toBbox.top + toBbox.height / 2 };
|
||||
svgDrawLine(svg, [lineStart, lineEnd], color, 'linear');
|
||||
svgDrawLine(
|
||||
svg,
|
||||
[
|
||||
{ x: fromBbox.left, y: fromBbox.top + fromBbox.height / 2 },
|
||||
{ x: fromBbox.left - config.nodeSpacing / 2, y: fromBbox.top + fromBbox.height / 2 },
|
||||
{ x: fromBbox.left - config.nodeSpacing / 2, y: lineStart.y },
|
||||
lineStart
|
||||
],
|
||||
color
|
||||
);
|
||||
} else {
|
||||
svgDrawLine(svg, [{
|
||||
'x': fromBbox.left,
|
||||
'y': fromBbox.top + fromBbox.height / 2
|
||||
}, {
|
||||
'x': fromBbox.left - config.nodeSpacing / 2,
|
||||
'y': fromBbox.top + fromBbox.height / 2
|
||||
}, {
|
||||
'x': fromBbox.left - config.nodeSpacing / 2,
|
||||
'y': toBbox.top + toBbox.height / 2
|
||||
}, {
|
||||
'x': toBbox.left + toBbox.width,
|
||||
'y': toBbox.top + toBbox.height / 2
|
||||
}], color)
|
||||
svgDrawLine(
|
||||
svg,
|
||||
[
|
||||
{
|
||||
x: fromBbox.left,
|
||||
y: fromBbox.top + fromBbox.height / 2
|
||||
},
|
||||
{
|
||||
x: fromBbox.left - config.nodeSpacing / 2,
|
||||
y: fromBbox.top + fromBbox.height / 2
|
||||
},
|
||||
{
|
||||
x: fromBbox.left - config.nodeSpacing / 2,
|
||||
y: toBbox.top + toBbox.height / 2
|
||||
},
|
||||
{
|
||||
x: toBbox.left + toBbox.width,
|
||||
y: toBbox.top + toBbox.height / 2
|
||||
}
|
||||
],
|
||||
color
|
||||
);
|
||||
}
|
||||
break
|
||||
break;
|
||||
case 'BT':
|
||||
// + (fromBbox)
|
||||
// |
|
||||
// |
|
||||
// + (toBbox)
|
||||
if (toBbox.top - fromBbox.top > config.nodeSpacing) {
|
||||
const lineStart = { x: toBbox.left + toBbox.width / 2, y: fromBbox.top + fromBbox.height + config.nodeSpacing }
|
||||
const lineEnd = { x: toBbox.left + toBbox.width / 2, y: toBbox.top }
|
||||
svgDrawLine(svg, [lineStart, lineEnd], color, 'linear')
|
||||
svgDrawLine(svg, [
|
||||
{ x: fromBbox.left + fromBbox.width / 2, y: fromBbox.top + fromBbox.height },
|
||||
{ x: fromBbox.left + fromBbox.width / 2, y: fromBbox.top + fromBbox.height + config.nodeSpacing / 2 },
|
||||
{ x: toBbox.left + toBbox.width / 2, y: lineStart.y - config.nodeSpacing / 2 },
|
||||
lineStart], color)
|
||||
const lineStart = {
|
||||
x: toBbox.left + toBbox.width / 2,
|
||||
y: fromBbox.top + fromBbox.height + config.nodeSpacing
|
||||
};
|
||||
const lineEnd = { x: toBbox.left + toBbox.width / 2, y: toBbox.top };
|
||||
svgDrawLine(svg, [lineStart, lineEnd], color, 'linear');
|
||||
svgDrawLine(
|
||||
svg,
|
||||
[
|
||||
{ x: fromBbox.left + fromBbox.width / 2, y: fromBbox.top + fromBbox.height },
|
||||
{
|
||||
x: fromBbox.left + fromBbox.width / 2,
|
||||
y: fromBbox.top + fromBbox.height + config.nodeSpacing / 2
|
||||
},
|
||||
{ x: toBbox.left + toBbox.width / 2, y: lineStart.y - config.nodeSpacing / 2 },
|
||||
lineStart
|
||||
],
|
||||
color
|
||||
);
|
||||
} else {
|
||||
svgDrawLine(svg, [{
|
||||
'x': fromBbox.left + fromBbox.width / 2,
|
||||
'y': fromBbox.top + fromBbox.height
|
||||
}, {
|
||||
'x': fromBbox.left + fromBbox.width / 2,
|
||||
'y': fromBbox.top + config.nodeSpacing / 2
|
||||
}, {
|
||||
'x': toBbox.left + toBbox.width / 2,
|
||||
'y': toBbox.top - config.nodeSpacing / 2
|
||||
}, {
|
||||
'x': toBbox.left + toBbox.width / 2,
|
||||
'y': toBbox.top
|
||||
}], color)
|
||||
svgDrawLine(
|
||||
svg,
|
||||
[
|
||||
{
|
||||
x: fromBbox.left + fromBbox.width / 2,
|
||||
y: fromBbox.top + fromBbox.height
|
||||
},
|
||||
{
|
||||
x: fromBbox.left + fromBbox.width / 2,
|
||||
y: fromBbox.top + config.nodeSpacing / 2
|
||||
},
|
||||
{
|
||||
x: toBbox.left + toBbox.width / 2,
|
||||
y: toBbox.top - config.nodeSpacing / 2
|
||||
},
|
||||
{
|
||||
x: toBbox.left + toBbox.width / 2,
|
||||
y: toBbox.top
|
||||
}
|
||||
],
|
||||
color
|
||||
);
|
||||
}
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function cloneNode (svg, selector) {
|
||||
return svg.select(selector).node().cloneNode(true)
|
||||
function cloneNode(svg, selector) {
|
||||
return svg
|
||||
.select(selector)
|
||||
.node()
|
||||
.cloneNode(true);
|
||||
}
|
||||
|
||||
function renderCommitHistory (svg, commitid, branches, direction) {
|
||||
let commit
|
||||
const numCommits = Object.keys(allCommitsDict).length
|
||||
function renderCommitHistory(svg, commitid, branches, direction) {
|
||||
let commit;
|
||||
const numCommits = Object.keys(allCommitsDict).length;
|
||||
if (typeof commitid === 'string') {
|
||||
do {
|
||||
commit = allCommitsDict[commitid]
|
||||
logger.debug('in renderCommitHistory', commit.id, commit.seq)
|
||||
commit = allCommitsDict[commitid];
|
||||
logger.debug('in renderCommitHistory', commit.id, commit.seq);
|
||||
if (svg.select('#node-' + commitid).size() > 0) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
svg
|
||||
.append(function () {
|
||||
return cloneNode(svg, '#def-commit')
|
||||
.append(function() {
|
||||
return cloneNode(svg, '#def-commit');
|
||||
})
|
||||
.attr('class', 'commit')
|
||||
.attr('id', function () {
|
||||
return 'node-' + commit.id
|
||||
.attr('id', function() {
|
||||
return 'node-' + commit.id;
|
||||
})
|
||||
.attr('transform', function () {
|
||||
.attr('transform', function() {
|
||||
switch (direction) {
|
||||
case 'LR':
|
||||
return 'translate(' + (commit.seq * config.nodeSpacing + config.leftMargin) + ', ' +
|
||||
(branchNum * config.branchOffset) + ')'
|
||||
return (
|
||||
'translate(' +
|
||||
(commit.seq * config.nodeSpacing + config.leftMargin) +
|
||||
', ' +
|
||||
branchNum * config.branchOffset +
|
||||
')'
|
||||
);
|
||||
case 'BT':
|
||||
return 'translate(' + (branchNum * config.branchOffset + config.leftMargin) + ', ' +
|
||||
((numCommits - commit.seq) * config.nodeSpacing) + ')'
|
||||
return (
|
||||
'translate(' +
|
||||
(branchNum * config.branchOffset + config.leftMargin) +
|
||||
', ' +
|
||||
(numCommits - commit.seq) * config.nodeSpacing +
|
||||
')'
|
||||
);
|
||||
}
|
||||
})
|
||||
.attr('fill', config.nodeFillColor)
|
||||
.attr('stroke', config.nodeStrokeColor)
|
||||
.attr('stroke-width', config.nodeStrokeWidth)
|
||||
.attr('stroke-width', config.nodeStrokeWidth);
|
||||
|
||||
let branch
|
||||
let branch;
|
||||
for (let branchName in branches) {
|
||||
if (branches[branchName].commit === commit) {
|
||||
branch = branches[branchName]
|
||||
break
|
||||
branch = branches[branchName];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (branch) {
|
||||
logger.debug('found branch ', branch.name)
|
||||
svg.select('#node-' + commit.id + ' p')
|
||||
logger.debug('found branch ', branch.name);
|
||||
svg
|
||||
.select('#node-' + commit.id + ' p')
|
||||
.append('xhtml:span')
|
||||
.attr('class', 'branch-label')
|
||||
.text(branch.name + ', ')
|
||||
.text(branch.name + ', ');
|
||||
}
|
||||
svg.select('#node-' + commit.id + ' p')
|
||||
svg
|
||||
.select('#node-' + commit.id + ' p')
|
||||
.append('xhtml:span')
|
||||
.attr('class', 'commit-id')
|
||||
.text(commit.id)
|
||||
.text(commit.id);
|
||||
if (commit.message !== '' && direction === 'BT') {
|
||||
svg.select('#node-' + commit.id + ' p')
|
||||
svg
|
||||
.select('#node-' + commit.id + ' p')
|
||||
.append('xhtml:span')
|
||||
.attr('class', 'commit-msg')
|
||||
.text(', ' + commit.message)
|
||||
.text(', ' + commit.message);
|
||||
}
|
||||
commitid = commit.parent
|
||||
} while (commitid && allCommitsDict[commitid])
|
||||
commitid = commit.parent;
|
||||
} while (commitid && allCommitsDict[commitid]);
|
||||
}
|
||||
|
||||
if (Array.isArray(commitid)) {
|
||||
logger.debug('found merge commmit', commitid)
|
||||
renderCommitHistory(svg, commitid[0], branches, direction)
|
||||
branchNum++
|
||||
renderCommitHistory(svg, commitid[1], branches, direction)
|
||||
branchNum--
|
||||
logger.debug('found merge commmit', commitid);
|
||||
renderCommitHistory(svg, commitid[0], branches, direction);
|
||||
branchNum++;
|
||||
renderCommitHistory(svg, commitid[1], branches, direction);
|
||||
branchNum--;
|
||||
}
|
||||
}
|
||||
|
||||
function renderLines (svg, commit, direction, branchColor) {
|
||||
branchColor = branchColor || 0
|
||||
function renderLines(svg, commit, direction, branchColor) {
|
||||
branchColor = branchColor || 0;
|
||||
while (commit.seq > 0 && !commit.lineDrawn) {
|
||||
if (typeof commit.parent === 'string') {
|
||||
svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor)
|
||||
commit.lineDrawn = true
|
||||
commit = allCommitsDict[commit.parent]
|
||||
svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor);
|
||||
commit.lineDrawn = true;
|
||||
commit = allCommitsDict[commit.parent];
|
||||
} else if (Array.isArray(commit.parent)) {
|
||||
svgDrawLineForCommits(svg, commit.id, commit.parent[0], direction, branchColor)
|
||||
svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1)
|
||||
renderLines(svg, allCommitsDict[commit.parent[1]], direction, branchColor + 1)
|
||||
commit.lineDrawn = true
|
||||
commit = allCommitsDict[commit.parent[0]]
|
||||
svgDrawLineForCommits(svg, commit.id, commit.parent[0], direction, branchColor);
|
||||
svgDrawLineForCommits(svg, commit.id, commit.parent[1], direction, branchColor + 1);
|
||||
renderLines(svg, allCommitsDict[commit.parent[1]], direction, branchColor + 1);
|
||||
commit.lineDrawn = true;
|
||||
commit = allCommitsDict[commit.parent[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const draw = function (txt, id, ver) {
|
||||
export const draw = function(txt, id, ver) {
|
||||
try {
|
||||
const parser = gitGraphParser.parser
|
||||
parser.yy = db
|
||||
const parser = gitGraphParser.parser;
|
||||
parser.yy = db;
|
||||
|
||||
logger.debug('in gitgraph renderer', txt, id, ver)
|
||||
logger.debug('in gitgraph renderer', txt, id, ver);
|
||||
// Parse the graph definition
|
||||
parser.parse(txt + '\n')
|
||||
parser.parse(txt + '\n');
|
||||
|
||||
config = _.assign(config, apiConfig, db.getOptions())
|
||||
logger.debug('effective options', config)
|
||||
const direction = db.getDirection()
|
||||
allCommitsDict = db.getCommits()
|
||||
const branches = db.getBranchesAsObjArray()
|
||||
config = _.assign(config, apiConfig, db.getOptions());
|
||||
logger.debug('effective options', config);
|
||||
const direction = db.getDirection();
|
||||
allCommitsDict = db.getCommits();
|
||||
const branches = db.getBranchesAsObjArray();
|
||||
if (direction === 'BT') {
|
||||
config.nodeLabel.x = branches.length * config.branchOffset
|
||||
config.nodeLabel.width = '100%'
|
||||
config.nodeLabel.y = -1 * 2 * config.nodeRadius
|
||||
config.nodeLabel.x = branches.length * config.branchOffset;
|
||||
config.nodeLabel.width = '100%';
|
||||
config.nodeLabel.y = -1 * 2 * config.nodeRadius;
|
||||
}
|
||||
const svg = d3.select(`[id="${id}"]`)
|
||||
svgCreateDefs(svg)
|
||||
branchNum = 1
|
||||
const svg = d3.select(`[id="${id}"]`);
|
||||
svgCreateDefs(svg);
|
||||
branchNum = 1;
|
||||
for (let branch in branches) {
|
||||
const v = branches[branch]
|
||||
renderCommitHistory(svg, v.commit.id, branches, direction)
|
||||
renderLines(svg, v.commit, direction)
|
||||
branchNum++
|
||||
const v = branches[branch];
|
||||
renderCommitHistory(svg, v.commit.id, branches, direction);
|
||||
renderLines(svg, v.commit, direction);
|
||||
branchNum++;
|
||||
}
|
||||
svg.attr('height', function () {
|
||||
if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing
|
||||
return (branches.length + 1) * config.branchOffset
|
||||
})
|
||||
svg.attr('height', function() {
|
||||
if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing;
|
||||
return (branches.length + 1) * config.branchOffset;
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Error while rendering gitgraph')
|
||||
logger.error(e.message)
|
||||
logger.error('Error while rendering gitgraph');
|
||||
logger.error(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw
|
||||
}
|
||||
};
|
||||
|
@ -1,15 +1,15 @@
|
||||
/* eslint-env jasmine */
|
||||
describe('when parsing an info graph it', function () {
|
||||
var ex
|
||||
beforeEach(function () {
|
||||
ex = require('./parser/info').parser
|
||||
ex.yy = require('./infoDb')
|
||||
})
|
||||
describe('when parsing an info graph it', function() {
|
||||
var ex;
|
||||
beforeEach(function() {
|
||||
ex = require('./parser/info').parser;
|
||||
ex.yy = require('./infoDb');
|
||||
});
|
||||
|
||||
it('should handle an info definition', function () {
|
||||
it('should handle an info definition', function() {
|
||||
var str = `info
|
||||
showInfo`
|
||||
showInfo`;
|
||||
|
||||
ex.parse(str)
|
||||
})
|
||||
})
|
||||
ex.parse(str);
|
||||
});
|
||||
});
|
||||
|
@ -1,27 +1,27 @@
|
||||
/**
|
||||
* Created by knut on 15-01-14.
|
||||
*/
|
||||
import { logger } from '../../logger'
|
||||
import { logger } from '../../logger';
|
||||
|
||||
var message = ''
|
||||
var info = false
|
||||
var message = '';
|
||||
var info = false;
|
||||
|
||||
export const setMessage = txt => {
|
||||
logger.debug('Setting message to: ' + txt)
|
||||
message = txt
|
||||
}
|
||||
logger.debug('Setting message to: ' + txt);
|
||||
message = txt;
|
||||
};
|
||||
|
||||
export const getMessage = () => {
|
||||
return message
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
export const setInfo = inf => {
|
||||
info = inf
|
||||
}
|
||||
info = inf;
|
||||
};
|
||||
|
||||
export const getInfo = () => {
|
||||
return info
|
||||
}
|
||||
return info;
|
||||
};
|
||||
|
||||
// export const parseError = (err, hash) => {
|
||||
// global.mermaidAPI.parseError(err, hash)
|
||||
@ -33,4 +33,4 @@ export default {
|
||||
setInfo,
|
||||
getInfo
|
||||
// parseError
|
||||
}
|
||||
};
|
||||
|
@ -1,20 +1,19 @@
|
||||
/**
|
||||
* Created by knut on 14-12-11.
|
||||
*/
|
||||
import * as d3 from 'd3'
|
||||
import db from './infoDb'
|
||||
import infoParser from './parser/info'
|
||||
import { logger } from '../../logger'
|
||||
import * as d3 from 'd3';
|
||||
import db from './infoDb';
|
||||
import infoParser from './parser/info';
|
||||
import { logger } from '../../logger';
|
||||
|
||||
const conf = {
|
||||
}
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf)
|
||||
const conf = {};
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
keys.forEach(function (key) {
|
||||
conf[key] = cnf[key]
|
||||
})
|
||||
}
|
||||
keys.forEach(function(key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a an info picture in the tag with id: id based on the graph definition in text.
|
||||
@ -23,16 +22,16 @@ export const setConf = function (cnf) {
|
||||
*/
|
||||
export const draw = (txt, id, ver) => {
|
||||
try {
|
||||
const parser = infoParser.parser
|
||||
parser.yy = db
|
||||
logger.debug('Renering info diagram\n' + txt)
|
||||
const parser = infoParser.parser;
|
||||
parser.yy = db;
|
||||
logger.debug('Renering info diagram\n' + txt);
|
||||
// Parse the graph definition
|
||||
parser.parse(txt)
|
||||
logger.debug('Parsed info diagram')
|
||||
parser.parse(txt);
|
||||
logger.debug('Parsed info diagram');
|
||||
// Fetch the default direction, use TD if none was found
|
||||
const svg = d3.select('#' + id)
|
||||
const svg = d3.select('#' + id);
|
||||
|
||||
const g = svg.append('g')
|
||||
const g = svg.append('g');
|
||||
|
||||
g.append('text') // text label for the x axis
|
||||
.attr('x', 100)
|
||||
@ -40,18 +39,18 @@ export const draw = (txt, id, ver) => {
|
||||
.attr('class', 'version')
|
||||
.attr('font-size', '32px')
|
||||
.style('text-anchor', 'middle')
|
||||
.text('v ' + ver)
|
||||
.text('v ' + ver);
|
||||
|
||||
svg.attr('height', 100)
|
||||
svg.attr('width', 400)
|
||||
svg.attr('height', 100);
|
||||
svg.attr('width', 400);
|
||||
// svg.attr('viewBox', '0 0 300 150');
|
||||
} catch (e) {
|
||||
logger.error('Error while rendering info diagram')
|
||||
logger.error(e.message)
|
||||
logger.error('Error while rendering info diagram');
|
||||
logger.error(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw
|
||||
}
|
||||
};
|
||||
|
@ -1,37 +1,36 @@
|
||||
|
||||
/* eslint-env jasmine */
|
||||
import pieDb from '../pieDb'
|
||||
import pie from './pie'
|
||||
import { setConfig } from '../../../config'
|
||||
import pieDb from '../pieDb';
|
||||
import pie from './pie';
|
||||
import { setConfig } from '../../../config';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict'
|
||||
})
|
||||
});
|
||||
|
||||
describe('when parsing pie', function () {
|
||||
beforeEach(function () {
|
||||
pie.parser.yy = pieDb
|
||||
pie.parser.yy.clear()
|
||||
})
|
||||
it('should handle simple pie', function () {
|
||||
const res = pie.parser.parse('pie \n"ash" : 60\n"bat" : 40\n')
|
||||
const sections = pieDb.getSections()
|
||||
console.log('sections: ', sections)
|
||||
const section1 = sections['ash']
|
||||
expect(section1).toBe(60)
|
||||
})
|
||||
describe('when parsing pie', function() {
|
||||
beforeEach(function() {
|
||||
pie.parser.yy = pieDb;
|
||||
pie.parser.yy.clear();
|
||||
});
|
||||
it('should handle simple pie', function() {
|
||||
const res = pie.parser.parse('pie \n"ash" : 60\n"bat" : 40\n');
|
||||
const sections = pieDb.getSections();
|
||||
console.log('sections: ', sections);
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
});
|
||||
|
||||
it('should handle simple pie with positive decimal', function () {
|
||||
const res = pie.parser.parse('pie \n"ash" : 60.67\n"bat" : 40\n')
|
||||
const sections = pieDb.getSections()
|
||||
console.log('sections: ', sections)
|
||||
const section1 = sections['ash']
|
||||
expect(section1).toBe(60.67)
|
||||
})
|
||||
it('should handle simple pie with positive decimal', function() {
|
||||
const res = pie.parser.parse('pie \n"ash" : 60.67\n"bat" : 40\n');
|
||||
const sections = pieDb.getSections();
|
||||
console.log('sections: ', sections);
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60.67);
|
||||
});
|
||||
|
||||
it('should handle simple pie with negative decimal', function () {
|
||||
expect(()=>{
|
||||
it('should handle simple pie with negative decimal', function() {
|
||||
expect(() => {
|
||||
pie.parser.parse('pie \n"ash" : 60.67\n"bat" : 40..12\n');
|
||||
}).toThrowError();
|
||||
})
|
||||
})
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
||||
|
@ -1,40 +1,40 @@
|
||||
/**
|
||||
*
|
||||
*/
|
||||
import { logger } from '../../logger'
|
||||
import { logger } from '../../logger';
|
||||
|
||||
let sections = {}
|
||||
let title = ''
|
||||
let sections = {};
|
||||
let title = '';
|
||||
|
||||
const addSection = function (id, value) {
|
||||
const addSection = function(id, value) {
|
||||
if (typeof sections[id] === 'undefined') {
|
||||
sections[id] = value
|
||||
logger.debug('Added new section :', id)
|
||||
sections[id] = value;
|
||||
logger.debug('Added new section :', id);
|
||||
// console.log('Added new section:', id, value)
|
||||
}
|
||||
}
|
||||
const getSections = () => sections
|
||||
};
|
||||
const getSections = () => sections;
|
||||
|
||||
const setTitle = function (txt) {
|
||||
title = txt
|
||||
}
|
||||
const setTitle = function(txt) {
|
||||
title = txt;
|
||||
};
|
||||
|
||||
const getTitle = function () {
|
||||
return title
|
||||
}
|
||||
const cleanupValue = function (value) {
|
||||
const getTitle = function() {
|
||||
return title;
|
||||
};
|
||||
const cleanupValue = function(value) {
|
||||
if (value.substring(0, 1) === ':') {
|
||||
value = value.substring(1).trim()
|
||||
return Number(value.trim())
|
||||
value = value.substring(1).trim();
|
||||
return Number(value.trim());
|
||||
} else {
|
||||
return Number(value.trim())
|
||||
return Number(value.trim());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const clear = function () {
|
||||
sections = {}
|
||||
title = ''
|
||||
}
|
||||
const clear = function() {
|
||||
sections = {};
|
||||
title = '';
|
||||
};
|
||||
// export const parseError = (err, hash) => {
|
||||
// global.mermaidAPI.parseError(err, hash)
|
||||
// }
|
||||
@ -47,4 +47,4 @@ export default {
|
||||
setTitle,
|
||||
getTitle
|
||||
// parseError
|
||||
}
|
||||
};
|
||||
|
@ -1,83 +1,87 @@
|
||||
/**
|
||||
* Created by AshishJ on 11-09-2019.
|
||||
*/
|
||||
import * as d3 from 'd3'
|
||||
import pieData from './pieDb'
|
||||
import pieParser from './parser/pie'
|
||||
import { logger } from '../../logger'
|
||||
import * as d3 from 'd3';
|
||||
import pieData from './pieDb';
|
||||
import pieParser from './parser/pie';
|
||||
import { logger } from '../../logger';
|
||||
|
||||
const conf = {
|
||||
}
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf)
|
||||
const conf = {};
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
keys.forEach(function (key) {
|
||||
conf[key] = cnf[key]
|
||||
})
|
||||
}
|
||||
keys.forEach(function(key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a Pie Chart with the data given in text.
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
let w
|
||||
let w;
|
||||
export const draw = (txt, id, ver) => {
|
||||
try {
|
||||
const parser = pieParser.parser
|
||||
parser.yy = pieData
|
||||
logger.debug('Rendering info diagram\n' + txt)
|
||||
const parser = pieParser.parser;
|
||||
parser.yy = pieData;
|
||||
logger.debug('Rendering info diagram\n' + txt);
|
||||
// Parse the Pie Chart definition
|
||||
parser.yy.clear()
|
||||
parser.parse(txt)
|
||||
logger.debug('Parsed info diagram')
|
||||
const elem = document.getElementById(id)
|
||||
w = elem.parentElement.offsetWidth
|
||||
parser.yy.clear();
|
||||
parser.parse(txt);
|
||||
logger.debug('Parsed info diagram');
|
||||
const elem = document.getElementById(id);
|
||||
w = elem.parentElement.offsetWidth;
|
||||
|
||||
if (typeof w === 'undefined') {
|
||||
w = 1200
|
||||
w = 1200;
|
||||
}
|
||||
|
||||
if (typeof conf.useWidth !== 'undefined') {
|
||||
w = conf.useWidth
|
||||
w = conf.useWidth;
|
||||
}
|
||||
const h = 450
|
||||
elem.setAttribute('height', '100%')
|
||||
const h = 450;
|
||||
elem.setAttribute('height', '100%');
|
||||
// Set viewBox
|
||||
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h)
|
||||
elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
|
||||
|
||||
// Fetch the default direction, use TD if none was found
|
||||
|
||||
var width = w// 450
|
||||
var height = 450
|
||||
var margin = 40
|
||||
var width = w; // 450
|
||||
var height = 450;
|
||||
var margin = 40;
|
||||
|
||||
var radius = Math.min(width, height) / 2 - margin
|
||||
var radius = Math.min(width, height) / 2 - margin;
|
||||
|
||||
var svg = d3.select('#' + id).append('svg')
|
||||
var svg = d3
|
||||
.select('#' + id)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.append('g')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||
|
||||
var data = pieData.getSections()
|
||||
logger.info(data)
|
||||
var data = pieData.getSections();
|
||||
logger.info(data);
|
||||
|
||||
// set the color scale
|
||||
var color = d3.scaleOrdinal()
|
||||
var color = d3
|
||||
.scaleOrdinal()
|
||||
.domain(data)
|
||||
.range(d3.schemeSet2)
|
||||
.range(d3.schemeSet2);
|
||||
|
||||
// Compute the position of each group on the pie:
|
||||
var pie = d3.pie()
|
||||
.value(function (d) { return d.value })
|
||||
var dataReady = pie(d3.entries(data))
|
||||
var pie = d3.pie().value(function(d) {
|
||||
return d.value;
|
||||
});
|
||||
var dataReady = pie(d3.entries(data));
|
||||
// Now I know that group A goes from 0 degrees to x degrees and so on.
|
||||
|
||||
// shape helper to build arcs:
|
||||
var arcGenerator = d3.arc()
|
||||
var arcGenerator = d3
|
||||
.arc()
|
||||
.innerRadius(0)
|
||||
.outerRadius(radius)
|
||||
.outerRadius(radius);
|
||||
|
||||
// Build the pie chart: Basically, each part of the pie is a path that we build using the arc function.
|
||||
svg
|
||||
@ -86,10 +90,12 @@ export const draw = (txt, id, ver) => {
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', arcGenerator)
|
||||
.attr('fill', function (d) { return (color(d.data.key)) })
|
||||
.attr('fill', function(d) {
|
||||
return color(d.data.key);
|
||||
})
|
||||
.attr('stroke', 'black')
|
||||
.style('stroke-width', '2px')
|
||||
.style('opacity', 0.7)
|
||||
.style('opacity', 0.7);
|
||||
|
||||
// Now add the annotation. Use the centroid method to get the best coordinates
|
||||
svg
|
||||
@ -97,23 +103,28 @@ export const draw = (txt, id, ver) => {
|
||||
.data(dataReady)
|
||||
.enter()
|
||||
.append('text')
|
||||
.text(function (d) { return d.data.key })
|
||||
.attr('transform', function (d) { return 'translate(' + arcGenerator.centroid(d) + ')' })
|
||||
.text(function(d) {
|
||||
return d.data.key;
|
||||
})
|
||||
.attr('transform', function(d) {
|
||||
return 'translate(' + arcGenerator.centroid(d) + ')';
|
||||
})
|
||||
.style('text-anchor', 'middle')
|
||||
.style('font-size', 17)
|
||||
.style('font-size', 17);
|
||||
|
||||
svg.append('text')
|
||||
svg
|
||||
.append('text')
|
||||
.text(parser.yy.getTitle())
|
||||
.attr('x', 0)
|
||||
.attr('y', -(h - 50) / 2)
|
||||
.attr('class', 'pieTitleText')
|
||||
.attr('class', 'pieTitleText');
|
||||
} catch (e) {
|
||||
logger.error('Error while rendering info diagram')
|
||||
logger.error(e.message)
|
||||
logger.error('Error while rendering info diagram');
|
||||
logger.error(e.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
setConf,
|
||||
draw
|
||||
}
|
||||
};
|
||||
|
@ -1,51 +1,53 @@
|
||||
import { logger } from '../../logger'
|
||||
import { logger } from '../../logger';
|
||||
|
||||
let actors = {}
|
||||
let messages = []
|
||||
const notes = []
|
||||
let title = ''
|
||||
let actors = {};
|
||||
let messages = [];
|
||||
const notes = [];
|
||||
let title = '';
|
||||
|
||||
export const addActor = function (id, name, description) {
|
||||
export const addActor = function(id, name, description) {
|
||||
// Don't allow description nulling
|
||||
const old = actors[id]
|
||||
if (old && name === old.name && description == null) return
|
||||
const old = actors[id];
|
||||
if (old && name === old.name && description == null) return;
|
||||
|
||||
// Don't allow null descriptions, either
|
||||
if (description == null) description = name
|
||||
if (description == null) description = name;
|
||||
|
||||
actors[id] = { name: name, description: description }
|
||||
}
|
||||
actors[id] = { name: name, description: description };
|
||||
};
|
||||
|
||||
export const addMessage = function (idFrom, idTo, message, answer) {
|
||||
messages.push({ from: idFrom, to: idTo, message: message, answer: answer })
|
||||
}
|
||||
export const addMessage = function(idFrom, idTo, message, answer) {
|
||||
messages.push({ from: idFrom, to: idTo, message: message, answer: answer });
|
||||
};
|
||||
|
||||
export const addSignal = function (idFrom, idTo, message, messageType) {
|
||||
logger.debug('Adding message from=' + idFrom + ' to=' + idTo + ' message=' + message + ' type=' + messageType)
|
||||
messages.push({ from: idFrom, to: idTo, message: message, type: messageType })
|
||||
}
|
||||
export const addSignal = function(idFrom, idTo, message, messageType) {
|
||||
logger.debug(
|
||||
'Adding message from=' + idFrom + ' to=' + idTo + ' message=' + message + ' type=' + messageType
|
||||
);
|
||||
messages.push({ from: idFrom, to: idTo, message: message, type: messageType });
|
||||
};
|
||||
|
||||
export const getMessages = function () {
|
||||
return messages
|
||||
}
|
||||
export const getMessages = function() {
|
||||
return messages;
|
||||
};
|
||||
|
||||
export const getActors = function () {
|
||||
return actors
|
||||
}
|
||||
export const getActor = function (id) {
|
||||
return actors[id]
|
||||
}
|
||||
export const getActorKeys = function () {
|
||||
return Object.keys(actors)
|
||||
}
|
||||
export const getTitle = function () {
|
||||
return title
|
||||
}
|
||||
export const getActors = function() {
|
||||
return actors;
|
||||
};
|
||||
export const getActor = function(id) {
|
||||
return actors[id];
|
||||
};
|
||||
export const getActorKeys = function() {
|
||||
return Object.keys(actors);
|
||||
};
|
||||
export const getTitle = function() {
|
||||
return title;
|
||||
};
|
||||
|
||||
export const clear = function () {
|
||||
actors = {}
|
||||
messages = []
|
||||
}
|
||||
export const clear = function() {
|
||||
actors = {};
|
||||
messages = [];
|
||||
};
|
||||
|
||||
export const LINETYPE = {
|
||||
SOLID: 0,
|
||||
@ -69,97 +71,103 @@ export const LINETYPE = {
|
||||
PAR_END: 21,
|
||||
RECT_START: 22,
|
||||
RECT_END: 23
|
||||
}
|
||||
};
|
||||
|
||||
export const ARROWTYPE = {
|
||||
FILLED: 0,
|
||||
OPEN: 1
|
||||
}
|
||||
};
|
||||
|
||||
export const PLACEMENT = {
|
||||
LEFTOF: 0,
|
||||
RIGHTOF: 1,
|
||||
OVER: 2
|
||||
}
|
||||
};
|
||||
|
||||
export const addNote = function (actor, placement, message) {
|
||||
const note = { actor: actor, placement: placement, message: message }
|
||||
export const addNote = function(actor, placement, message) {
|
||||
const note = { actor: actor, placement: placement, message: message };
|
||||
|
||||
// Coerce actor into a [to, from, ...] array
|
||||
const actors = [].concat(actor, actor)
|
||||
const actors = [].concat(actor, actor);
|
||||
|
||||
notes.push(note)
|
||||
messages.push({ from: actors[0], to: actors[1], message: message, type: LINETYPE.NOTE, placement: placement })
|
||||
}
|
||||
notes.push(note);
|
||||
messages.push({
|
||||
from: actors[0],
|
||||
to: actors[1],
|
||||
message: message,
|
||||
type: LINETYPE.NOTE,
|
||||
placement: placement
|
||||
});
|
||||
};
|
||||
|
||||
export const setTitle = function (titleText) {
|
||||
title = titleText
|
||||
}
|
||||
export const setTitle = function(titleText) {
|
||||
title = titleText;
|
||||
};
|
||||
|
||||
export const apply = function (param) {
|
||||
export const apply = function(param) {
|
||||
if (param instanceof Array) {
|
||||
param.forEach(function (item) {
|
||||
apply(item)
|
||||
})
|
||||
param.forEach(function(item) {
|
||||
apply(item);
|
||||
});
|
||||
} else {
|
||||
switch (param.type) {
|
||||
case 'addActor':
|
||||
addActor(param.actor, param.actor, param.description)
|
||||
break
|
||||
addActor(param.actor, param.actor, param.description);
|
||||
break;
|
||||
case 'activeStart':
|
||||
addSignal(param.actor, undefined, undefined, param.signalType)
|
||||
break
|
||||
addSignal(param.actor, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'activeEnd':
|
||||
addSignal(param.actor, undefined, undefined, param.signalType)
|
||||
break
|
||||
addSignal(param.actor, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'addNote':
|
||||
addNote(param.actor, param.placement, param.text)
|
||||
break
|
||||
addNote(param.actor, param.placement, param.text);
|
||||
break;
|
||||
case 'addMessage':
|
||||
addSignal(param.from, param.to, param.msg, param.signalType)
|
||||
break
|
||||
addSignal(param.from, param.to, param.msg, param.signalType);
|
||||
break;
|
||||
case 'loopStart':
|
||||
addSignal(undefined, undefined, param.loopText, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, param.loopText, param.signalType);
|
||||
break;
|
||||
case 'loopEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'rectStart':
|
||||
addSignal(undefined, undefined, param.color, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, param.color, param.signalType);
|
||||
break;
|
||||
case 'rectEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'optStart':
|
||||
addSignal(undefined, undefined, param.optText, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, param.optText, param.signalType);
|
||||
break;
|
||||
case 'optEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'altStart':
|
||||
addSignal(undefined, undefined, param.altText, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, param.altText, param.signalType);
|
||||
break;
|
||||
case 'else':
|
||||
addSignal(undefined, undefined, param.altText, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, param.altText, param.signalType);
|
||||
break;
|
||||
case 'altEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
case 'setTitle':
|
||||
setTitle(param.text)
|
||||
break
|
||||
setTitle(param.text);
|
||||
break;
|
||||
case 'parStart':
|
||||
addSignal(undefined, undefined, param.parText, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, param.parText, param.signalType);
|
||||
break;
|
||||
case 'and':
|
||||
addSignal(undefined, undefined, param.parText, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, param.parText, param.signalType);
|
||||
break;
|
||||
case 'parEnd':
|
||||
addSignal(undefined, undefined, undefined, param.signalType)
|
||||
break
|
||||
addSignal(undefined, undefined, undefined, param.signalType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
addActor,
|
||||
@ -177,4 +185,4 @@ export default {
|
||||
addNote,
|
||||
setTitle,
|
||||
apply
|
||||
}
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,13 @@
|
||||
import * as d3 from 'd3'
|
||||
import * as d3 from 'd3';
|
||||
|
||||
import svgDraw from './svgDraw'
|
||||
import { logger } from '../../logger'
|
||||
import { parser } from './parser/sequenceDiagram'
|
||||
import sequenceDb from './sequenceDb'
|
||||
import svgDraw from './svgDraw';
|
||||
import { logger } from '../../logger';
|
||||
import { parser } from './parser/sequenceDiagram';
|
||||
import sequenceDb from './sequenceDb';
|
||||
|
||||
parser.yy = sequenceDb
|
||||
parser.yy = sequenceDb;
|
||||
|
||||
const conf = {
|
||||
|
||||
diagramMarginX: 50,
|
||||
diagramMarginY: 30,
|
||||
// Margin between actors
|
||||
@ -38,7 +37,7 @@ const conf = {
|
||||
textPlacement: 'tspan',
|
||||
|
||||
showSequenceNumbers: false
|
||||
}
|
||||
};
|
||||
|
||||
export const bounds = {
|
||||
data: {
|
||||
@ -51,69 +50,69 @@ export const bounds = {
|
||||
|
||||
sequenceItems: [],
|
||||
activations: [],
|
||||
init: function () {
|
||||
this.sequenceItems = []
|
||||
this.activations = []
|
||||
init: function() {
|
||||
this.sequenceItems = [];
|
||||
this.activations = [];
|
||||
this.data = {
|
||||
startx: undefined,
|
||||
stopx: undefined,
|
||||
starty: undefined,
|
||||
stopy: undefined
|
||||
}
|
||||
this.verticalPos = 0
|
||||
};
|
||||
this.verticalPos = 0;
|
||||
},
|
||||
updateVal: function (obj, key, val, fun) {
|
||||
updateVal: function(obj, key, val, fun) {
|
||||
if (typeof obj[key] === 'undefined') {
|
||||
obj[key] = val
|
||||
obj[key] = val;
|
||||
} else {
|
||||
obj[key] = fun(val, obj[key])
|
||||
obj[key] = fun(val, obj[key]);
|
||||
}
|
||||
},
|
||||
updateBounds: function (startx, starty, stopx, stopy) {
|
||||
const _self = this
|
||||
let cnt = 0
|
||||
function updateFn (type) {
|
||||
return function updateItemBounds (item) {
|
||||
cnt++
|
||||
updateBounds: function(startx, starty, stopx, stopy) {
|
||||
const _self = this;
|
||||
let cnt = 0;
|
||||
function updateFn(type) {
|
||||
return function updateItemBounds(item) {
|
||||
cnt++;
|
||||
// The loop sequenceItems is a stack so the biggest margins in the beginning of the sequenceItems
|
||||
const n = _self.sequenceItems.length - cnt + 1
|
||||
const n = _self.sequenceItems.length - cnt + 1;
|
||||
|
||||
_self.updateVal(item, 'starty', starty - n * conf.boxMargin, Math.min)
|
||||
_self.updateVal(item, 'stopy', stopy + n * conf.boxMargin, Math.max)
|
||||
_self.updateVal(item, 'starty', starty - n * conf.boxMargin, Math.min);
|
||||
_self.updateVal(item, 'stopy', stopy + n * conf.boxMargin, Math.max);
|
||||
|
||||
_self.updateVal(bounds.data, 'startx', startx - n * conf.boxMargin, Math.min)
|
||||
_self.updateVal(bounds.data, 'stopx', stopx + n * conf.boxMargin, Math.max)
|
||||
_self.updateVal(bounds.data, 'startx', startx - n * conf.boxMargin, Math.min);
|
||||
_self.updateVal(bounds.data, 'stopx', stopx + n * conf.boxMargin, Math.max);
|
||||
|
||||
if (!(type === 'activation')) {
|
||||
_self.updateVal(item, 'startx', startx - n * conf.boxMargin, Math.min)
|
||||
_self.updateVal(item, 'stopx', stopx + n * conf.boxMargin, Math.max)
|
||||
_self.updateVal(item, 'startx', startx - n * conf.boxMargin, Math.min);
|
||||
_self.updateVal(item, 'stopx', stopx + n * conf.boxMargin, Math.max);
|
||||
|
||||
_self.updateVal(bounds.data, 'starty', starty - n * conf.boxMargin, Math.min)
|
||||
_self.updateVal(bounds.data, 'stopy', stopy + n * conf.boxMargin, Math.max)
|
||||
_self.updateVal(bounds.data, 'starty', starty - n * conf.boxMargin, Math.min);
|
||||
_self.updateVal(bounds.data, 'stopy', stopy + n * conf.boxMargin, Math.max);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.sequenceItems.forEach(updateFn())
|
||||
this.activations.forEach(updateFn('activation'))
|
||||
this.sequenceItems.forEach(updateFn());
|
||||
this.activations.forEach(updateFn('activation'));
|
||||
},
|
||||
insert: function (startx, starty, stopx, stopy) {
|
||||
const _startx = Math.min(startx, stopx)
|
||||
const _stopx = Math.max(startx, stopx)
|
||||
const _starty = Math.min(starty, stopy)
|
||||
const _stopy = Math.max(starty, stopy)
|
||||
insert: function(startx, starty, stopx, stopy) {
|
||||
const _startx = Math.min(startx, stopx);
|
||||
const _stopx = Math.max(startx, stopx);
|
||||
const _starty = Math.min(starty, stopy);
|
||||
const _stopy = Math.max(starty, stopy);
|
||||
|
||||
this.updateVal(bounds.data, 'startx', _startx, Math.min)
|
||||
this.updateVal(bounds.data, 'starty', _starty, Math.min)
|
||||
this.updateVal(bounds.data, 'stopx', _stopx, Math.max)
|
||||
this.updateVal(bounds.data, 'stopy', _stopy, Math.max)
|
||||
this.updateVal(bounds.data, 'startx', _startx, Math.min);
|
||||
this.updateVal(bounds.data, 'starty', _starty, Math.min);
|
||||
this.updateVal(bounds.data, 'stopx', _stopx, Math.max);
|
||||
this.updateVal(bounds.data, 'stopy', _stopy, Math.max);
|
||||
|
||||
this.updateBounds(_startx, _starty, _stopx, _stopy)
|
||||
this.updateBounds(_startx, _starty, _stopx, _stopy);
|
||||
},
|
||||
newActivation: function (message, diagram) {
|
||||
const actorRect = parser.yy.getActors()[message.from.actor]
|
||||
const stackedSize = actorActivations(message.from.actor).length
|
||||
const x = actorRect.x + conf.width / 2 + (stackedSize - 1) * conf.activationWidth / 2
|
||||
newActivation: function(message, diagram) {
|
||||
const actorRect = parser.yy.getActors()[message.from.actor];
|
||||
const stackedSize = actorActivations(message.from.actor).length;
|
||||
const x = actorRect.x + conf.width / 2 + ((stackedSize - 1) * conf.activationWidth) / 2;
|
||||
this.activations.push({
|
||||
startx: x,
|
||||
starty: this.verticalPos + 2,
|
||||
@ -121,59 +120,68 @@ export const bounds = {
|
||||
stopy: undefined,
|
||||
actor: message.from.actor,
|
||||
anchored: svgDraw.anchorElement(diagram)
|
||||
})
|
||||
});
|
||||
},
|
||||
endActivation: function (message) {
|
||||
endActivation: function(message) {
|
||||
// find most recent activation for given actor
|
||||
const lastActorActivationIdx = this.activations
|
||||
.map(function (activation) { return activation.actor })
|
||||
.lastIndexOf(message.from.actor)
|
||||
const activation = this.activations.splice(lastActorActivationIdx, 1)[0]
|
||||
return activation
|
||||
.map(function(activation) {
|
||||
return activation.actor;
|
||||
})
|
||||
.lastIndexOf(message.from.actor);
|
||||
const activation = this.activations.splice(lastActorActivationIdx, 1)[0];
|
||||
return activation;
|
||||
},
|
||||
newLoop: function (title, fill) {
|
||||
this.sequenceItems.push({ startx: undefined, starty: this.verticalPos, stopx: undefined, stopy: undefined, title: title, fill: fill })
|
||||
newLoop: function(title, fill) {
|
||||
this.sequenceItems.push({
|
||||
startx: undefined,
|
||||
starty: this.verticalPos,
|
||||
stopx: undefined,
|
||||
stopy: undefined,
|
||||
title: title,
|
||||
fill: fill
|
||||
});
|
||||
},
|
||||
endLoop: function () {
|
||||
const loop = this.sequenceItems.pop()
|
||||
return loop
|
||||
endLoop: function() {
|
||||
const loop = this.sequenceItems.pop();
|
||||
return loop;
|
||||
},
|
||||
addSectionToLoop: function (message) {
|
||||
const loop = this.sequenceItems.pop()
|
||||
loop.sections = loop.sections || []
|
||||
loop.sectionTitles = loop.sectionTitles || []
|
||||
loop.sections.push(bounds.getVerticalPos())
|
||||
loop.sectionTitles.push(message)
|
||||
this.sequenceItems.push(loop)
|
||||
addSectionToLoop: function(message) {
|
||||
const loop = this.sequenceItems.pop();
|
||||
loop.sections = loop.sections || [];
|
||||
loop.sectionTitles = loop.sectionTitles || [];
|
||||
loop.sections.push(bounds.getVerticalPos());
|
||||
loop.sectionTitles.push(message);
|
||||
this.sequenceItems.push(loop);
|
||||
},
|
||||
bumpVerticalPos: function (bump) {
|
||||
this.verticalPos = this.verticalPos + bump
|
||||
this.data.stopy = this.verticalPos
|
||||
bumpVerticalPos: function(bump) {
|
||||
this.verticalPos = this.verticalPos + bump;
|
||||
this.data.stopy = this.verticalPos;
|
||||
},
|
||||
getVerticalPos: function () {
|
||||
return this.verticalPos
|
||||
getVerticalPos: function() {
|
||||
return this.verticalPos;
|
||||
},
|
||||
getBounds: function () {
|
||||
return this.data
|
||||
getBounds: function() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _drawLongText = (text, x, y, g, width) => {
|
||||
let textHeight = 0
|
||||
const lines = text.split(/<br\/?>/ig)
|
||||
let textHeight = 0;
|
||||
const lines = text.split(/<br\/?>/gi);
|
||||
for (const line of lines) {
|
||||
const textObj = svgDraw.getTextObj()
|
||||
textObj.x = x
|
||||
textObj.y = y + textHeight
|
||||
textObj.textMargin = conf.noteMargin
|
||||
textObj.dy = '1em'
|
||||
textObj.text = line
|
||||
textObj.class = 'noteText'
|
||||
const textElem = svgDraw.drawText(g, textObj, width)
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height
|
||||
const textObj = svgDraw.getTextObj();
|
||||
textObj.x = x;
|
||||
textObj.y = y + textHeight;
|
||||
textObj.textMargin = conf.noteMargin;
|
||||
textObj.dy = '1em';
|
||||
textObj.text = line;
|
||||
textObj.class = 'noteText';
|
||||
const textElem = svgDraw.drawText(g, textObj, width);
|
||||
textHeight += (textElem._groups || textElem)[0][0].getBBox().height;
|
||||
}
|
||||
return textHeight
|
||||
}
|
||||
return textHeight;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
@ -181,22 +189,33 @@ const _drawLongText = (text, x, y, g, width) => {
|
||||
* @param pos The position if the actor in the liost of actors
|
||||
* @param description The text in the box
|
||||
*/
|
||||
const drawNote = function (elem, startx, verticalPos, msg, forceWidth) {
|
||||
const rect = svgDraw.getNoteRect()
|
||||
rect.x = startx
|
||||
rect.y = verticalPos
|
||||
rect.width = forceWidth || conf.width
|
||||
rect.class = 'note'
|
||||
const drawNote = function(elem, startx, verticalPos, msg, forceWidth) {
|
||||
const rect = svgDraw.getNoteRect();
|
||||
rect.x = startx;
|
||||
rect.y = verticalPos;
|
||||
rect.width = forceWidth || conf.width;
|
||||
rect.class = 'note';
|
||||
|
||||
let g = elem.append('g')
|
||||
const rectElem = svgDraw.drawRect(g, rect)
|
||||
let g = elem.append('g');
|
||||
const rectElem = svgDraw.drawRect(g, rect);
|
||||
|
||||
const textHeight = _drawLongText(msg.message, startx - 4, verticalPos + 24, g, rect.width - conf.noteMargin)
|
||||
const textHeight = _drawLongText(
|
||||
msg.message,
|
||||
startx - 4,
|
||||
verticalPos + 24,
|
||||
g,
|
||||
rect.width - conf.noteMargin
|
||||
);
|
||||
|
||||
bounds.insert(startx, verticalPos, startx + rect.width, verticalPos + 2 * conf.noteMargin + textHeight)
|
||||
rectElem.attr('height', textHeight + 2 * conf.noteMargin)
|
||||
bounds.bumpVerticalPos(textHeight + 2 * conf.noteMargin)
|
||||
}
|
||||
bounds.insert(
|
||||
startx,
|
||||
verticalPos,
|
||||
startx + rect.width,
|
||||
verticalPos + 2 * conf.noteMargin + textHeight
|
||||
);
|
||||
rectElem.attr('height', textHeight + 2 * conf.noteMargin);
|
||||
bounds.bumpVerticalPos(textHeight + 2 * conf.noteMargin);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a message
|
||||
@ -207,70 +226,104 @@ const drawNote = function (elem, startx, verticalPos, msg, forceWidth) {
|
||||
* @param txtCenter
|
||||
* @param msg
|
||||
*/
|
||||
const drawMessage = function (elem, startx, stopx, verticalPos, msg, sequenceIndex) {
|
||||
const g = elem.append('g')
|
||||
const txtCenter = startx + (stopx - startx) / 2
|
||||
const drawMessage = function(elem, startx, stopx, verticalPos, msg, sequenceIndex) {
|
||||
const g = elem.append('g');
|
||||
const txtCenter = startx + (stopx - startx) / 2;
|
||||
|
||||
const textElem = g.append('text') // text label for the x axis
|
||||
const textElem = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', txtCenter)
|
||||
.attr('y', verticalPos - 7)
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('class', 'messageText')
|
||||
.text(msg.message)
|
||||
.text(msg.message);
|
||||
|
||||
let textWidth = (textElem._groups || textElem)[0][0].getBBox().width
|
||||
let textWidth = (textElem._groups || textElem)[0][0].getBBox().width;
|
||||
|
||||
let line
|
||||
let line;
|
||||
if (startx === stopx) {
|
||||
if (conf.rightAngles) {
|
||||
line = g.append('path').attr('d', `M ${startx},${verticalPos} H ${startx + (conf.width / 2)} V ${verticalPos + 25} H ${startx}`)
|
||||
line = g
|
||||
.append('path')
|
||||
.attr(
|
||||
'd',
|
||||
`M ${startx},${verticalPos} H ${startx + conf.width / 2} V ${verticalPos +
|
||||
25} H ${startx}`
|
||||
);
|
||||
} else {
|
||||
line = g.append('path')
|
||||
.attr('d', 'M ' + startx + ',' + verticalPos + ' C ' + (startx + 60) + ',' + (verticalPos - 10) + ' ' + (startx + 60) + ',' +
|
||||
(verticalPos + 30) + ' ' + startx + ',' + (verticalPos + 20))
|
||||
line = g
|
||||
.append('path')
|
||||
.attr(
|
||||
'd',
|
||||
'M ' +
|
||||
startx +
|
||||
',' +
|
||||
verticalPos +
|
||||
' C ' +
|
||||
(startx + 60) +
|
||||
',' +
|
||||
(verticalPos - 10) +
|
||||
' ' +
|
||||
(startx + 60) +
|
||||
',' +
|
||||
(verticalPos + 30) +
|
||||
' ' +
|
||||
startx +
|
||||
',' +
|
||||
(verticalPos + 20)
|
||||
);
|
||||
}
|
||||
|
||||
bounds.bumpVerticalPos(30)
|
||||
const dx = Math.max(textWidth / 2, 100)
|
||||
bounds.insert(startx - dx, bounds.getVerticalPos() - 10, stopx + dx, bounds.getVerticalPos())
|
||||
bounds.bumpVerticalPos(30);
|
||||
const dx = Math.max(textWidth / 2, 100);
|
||||
bounds.insert(startx - dx, bounds.getVerticalPos() - 10, stopx + dx, bounds.getVerticalPos());
|
||||
} else {
|
||||
line = g.append('line')
|
||||
line.attr('x1', startx)
|
||||
line.attr('y1', verticalPos)
|
||||
line.attr('x2', stopx)
|
||||
line.attr('y2', verticalPos)
|
||||
bounds.insert(startx, bounds.getVerticalPos() - 10, stopx, bounds.getVerticalPos())
|
||||
line = g.append('line');
|
||||
line.attr('x1', startx);
|
||||
line.attr('y1', verticalPos);
|
||||
line.attr('x2', stopx);
|
||||
line.attr('y2', verticalPos);
|
||||
bounds.insert(startx, bounds.getVerticalPos() - 10, stopx, bounds.getVerticalPos());
|
||||
}
|
||||
// Make an SVG Container
|
||||
// Draw the line
|
||||
if (msg.type === parser.yy.LINETYPE.DOTTED || msg.type === parser.yy.LINETYPE.DOTTED_CROSS || msg.type === parser.yy.LINETYPE.DOTTED_OPEN) {
|
||||
line.style('stroke-dasharray', ('3, 3'))
|
||||
line.attr('class', 'messageLine1')
|
||||
if (
|
||||
msg.type === parser.yy.LINETYPE.DOTTED ||
|
||||
msg.type === parser.yy.LINETYPE.DOTTED_CROSS ||
|
||||
msg.type === parser.yy.LINETYPE.DOTTED_OPEN
|
||||
) {
|
||||
line.style('stroke-dasharray', '3, 3');
|
||||
line.attr('class', 'messageLine1');
|
||||
} else {
|
||||
line.attr('class', 'messageLine0')
|
||||
line.attr('class', 'messageLine0');
|
||||
}
|
||||
|
||||
let url = ''
|
||||
let url = '';
|
||||
if (conf.arrowMarkerAbsolute) {
|
||||
url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
|
||||
url = url.replace(/\(/g, '\\(')
|
||||
url = url.replace(/\)/g, '\\)')
|
||||
url =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
window.location.search;
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
line.attr('stroke-width', 2)
|
||||
line.attr('stroke', 'black')
|
||||
line.style('fill', 'none') // remove any fill colour
|
||||
line.attr('stroke-width', 2);
|
||||
line.attr('stroke', 'black');
|
||||
line.style('fill', 'none'); // remove any fill colour
|
||||
if (msg.type === parser.yy.LINETYPE.SOLID || msg.type === parser.yy.LINETYPE.DOTTED) {
|
||||
line.attr('marker-end', 'url(' + url + '#arrowhead)')
|
||||
line.attr('marker-end', 'url(' + url + '#arrowhead)');
|
||||
}
|
||||
|
||||
if (msg.type === parser.yy.LINETYPE.SOLID_CROSS || msg.type === parser.yy.LINETYPE.DOTTED_CROSS) {
|
||||
line.attr('marker-end', 'url(' + url + '#crosshead)')
|
||||
line.attr('marker-end', 'url(' + url + '#crosshead)');
|
||||
}
|
||||
|
||||
// add node number
|
||||
if (conf.showSequenceNumbers) {
|
||||
line.attr('marker-start', 'url(' + url + '#sequencenumber)')
|
||||
line.attr('marker-start', 'url(' + url + '#sequencenumber)');
|
||||
g.append('text')
|
||||
.attr('x', startx)
|
||||
.attr('y', verticalPos + 4)
|
||||
@ -279,263 +332,306 @@ const drawMessage = function (elem, startx, stopx, verticalPos, msg, sequenceInd
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('textLength', '16px')
|
||||
.attr('class', 'sequenceNumber')
|
||||
.text(sequenceIndex)
|
||||
.text(sequenceIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const drawActors = function (diagram, actors, actorKeys, verticalPos) {
|
||||
export const drawActors = function(diagram, actors, actorKeys, verticalPos) {
|
||||
// Draw the actors
|
||||
for (let i = 0; i < actorKeys.length; i++) {
|
||||
const key = actorKeys[i]
|
||||
const key = actorKeys[i];
|
||||
|
||||
// Add some rendering data to the object
|
||||
actors[key].x = i * conf.actorMargin + i * conf.width
|
||||
actors[key].y = verticalPos
|
||||
actors[key].width = conf.diagramMarginX
|
||||
actors[key].height = conf.diagramMarginY
|
||||
actors[key].x = i * conf.actorMargin + i * conf.width;
|
||||
actors[key].y = verticalPos;
|
||||
actors[key].width = conf.diagramMarginX;
|
||||
actors[key].height = conf.diagramMarginY;
|
||||
|
||||
// Draw the box with the attached line
|
||||
svgDraw.drawActor(diagram, actors[key].x, verticalPos, actors[key].description, conf)
|
||||
bounds.insert(actors[key].x, verticalPos, actors[key].x + conf.width, conf.height)
|
||||
svgDraw.drawActor(diagram, actors[key].x, verticalPos, actors[key].description, conf);
|
||||
bounds.insert(actors[key].x, verticalPos, actors[key].x + conf.width, conf.height);
|
||||
}
|
||||
|
||||
// Add a margin between the actor boxes and the first arrow
|
||||
bounds.bumpVerticalPos(conf.height)
|
||||
}
|
||||
bounds.bumpVerticalPos(conf.height);
|
||||
};
|
||||
|
||||
export const setConf = function (cnf) {
|
||||
const keys = Object.keys(cnf)
|
||||
export const setConf = function(cnf) {
|
||||
const keys = Object.keys(cnf);
|
||||
|
||||
keys.forEach(function (key) {
|
||||
conf[key] = cnf[key]
|
||||
})
|
||||
}
|
||||
keys.forEach(function(key) {
|
||||
conf[key] = cnf[key];
|
||||
});
|
||||
};
|
||||
|
||||
const actorActivations = function (actor) {
|
||||
return bounds.activations.filter(function (activation) {
|
||||
return activation.actor === actor
|
||||
})
|
||||
}
|
||||
const actorActivations = function(actor) {
|
||||
return bounds.activations.filter(function(activation) {
|
||||
return activation.actor === actor;
|
||||
});
|
||||
};
|
||||
|
||||
const actorFlowVerticaBounds = function (actor) {
|
||||
const actorFlowVerticaBounds = function(actor) {
|
||||
// handle multiple stacked activations for same actor
|
||||
const actors = parser.yy.getActors()
|
||||
const activations = actorActivations(actor)
|
||||
const actors = parser.yy.getActors();
|
||||
const activations = actorActivations(actor);
|
||||
|
||||
const left = activations.reduce(function (acc, activation) { return Math.min(acc, activation.startx) }, actors[actor].x + conf.width / 2)
|
||||
const right = activations.reduce(function (acc, activation) { return Math.max(acc, activation.stopx) }, actors[actor].x + conf.width / 2)
|
||||
return [left, right]
|
||||
}
|
||||
const left = activations.reduce(function(acc, activation) {
|
||||
return Math.min(acc, activation.startx);
|
||||
}, actors[actor].x + conf.width / 2);
|
||||
const right = activations.reduce(function(acc, activation) {
|
||||
return Math.max(acc, activation.stopx);
|
||||
}, actors[actor].x + conf.width / 2);
|
||||
return [left, right];
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
export const draw = function (text, id) {
|
||||
parser.yy.clear()
|
||||
parser.parse(text + '\n')
|
||||
export const draw = function(text, id) {
|
||||
parser.yy.clear();
|
||||
parser.parse(text + '\n');
|
||||
|
||||
bounds.init()
|
||||
const diagram = d3.select(`[id="${id}"]`)
|
||||
bounds.init();
|
||||
const diagram = d3.select(`[id="${id}"]`);
|
||||
|
||||
let startx
|
||||
let stopx
|
||||
let forceWidth
|
||||
let startx;
|
||||
let stopx;
|
||||
let forceWidth;
|
||||
|
||||
// Fetch data from the parsing
|
||||
const actors = parser.yy.getActors()
|
||||
const actorKeys = parser.yy.getActorKeys()
|
||||
const messages = parser.yy.getMessages()
|
||||
const title = parser.yy.getTitle()
|
||||
drawActors(diagram, actors, actorKeys, 0)
|
||||
const actors = parser.yy.getActors();
|
||||
const actorKeys = parser.yy.getActorKeys();
|
||||
const messages = parser.yy.getMessages();
|
||||
const title = parser.yy.getTitle();
|
||||
drawActors(diagram, actors, actorKeys, 0);
|
||||
|
||||
// The arrow head definition is attached to the svg once
|
||||
svgDraw.insertArrowHead(diagram)
|
||||
svgDraw.insertArrowCrossHead(diagram)
|
||||
svgDraw.insertSequenceNumber(diagram)
|
||||
svgDraw.insertArrowHead(diagram);
|
||||
svgDraw.insertArrowCrossHead(diagram);
|
||||
svgDraw.insertSequenceNumber(diagram);
|
||||
|
||||
function activeEnd (msg, verticalPos) {
|
||||
const activationData = bounds.endActivation(msg)
|
||||
function activeEnd(msg, verticalPos) {
|
||||
const activationData = bounds.endActivation(msg);
|
||||
if (activationData.starty + 18 > verticalPos) {
|
||||
activationData.starty = verticalPos - 6
|
||||
verticalPos += 12
|
||||
activationData.starty = verticalPos - 6;
|
||||
verticalPos += 12;
|
||||
}
|
||||
svgDraw.drawActivation(diagram, activationData, verticalPos, conf, actorActivations(msg.from.actor).length)
|
||||
svgDraw.drawActivation(
|
||||
diagram,
|
||||
activationData,
|
||||
verticalPos,
|
||||
conf,
|
||||
actorActivations(msg.from.actor).length
|
||||
);
|
||||
|
||||
bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos)
|
||||
bounds.insert(activationData.startx, verticalPos - 10, activationData.stopx, verticalPos);
|
||||
}
|
||||
|
||||
// const lastMsg
|
||||
|
||||
// Draw the messages/signals
|
||||
let sequenceIndex = 1
|
||||
messages.forEach(function (msg) {
|
||||
let loopData
|
||||
let sequenceIndex = 1;
|
||||
messages.forEach(function(msg) {
|
||||
let loopData;
|
||||
switch (msg.type) {
|
||||
case parser.yy.LINETYPE.NOTE:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
|
||||
startx = actors[msg.from].x
|
||||
stopx = actors[msg.to].x
|
||||
startx = actors[msg.from].x;
|
||||
stopx = actors[msg.to].x;
|
||||
|
||||
if (msg.placement === parser.yy.PLACEMENT.RIGHTOF) {
|
||||
drawNote(diagram, startx + (conf.width + conf.actorMargin) / 2, bounds.getVerticalPos(), msg)
|
||||
drawNote(
|
||||
diagram,
|
||||
startx + (conf.width + conf.actorMargin) / 2,
|
||||
bounds.getVerticalPos(),
|
||||
msg
|
||||
);
|
||||
} else if (msg.placement === parser.yy.PLACEMENT.LEFTOF) {
|
||||
drawNote(diagram, startx - (conf.width + conf.actorMargin) / 2, bounds.getVerticalPos(), msg)
|
||||
drawNote(
|
||||
diagram,
|
||||
startx - (conf.width + conf.actorMargin) / 2,
|
||||
bounds.getVerticalPos(),
|
||||
msg
|
||||
);
|
||||
} else if (msg.to === msg.from) {
|
||||
// Single-actor over
|
||||
drawNote(diagram, startx, bounds.getVerticalPos(), msg)
|
||||
drawNote(diagram, startx, bounds.getVerticalPos(), msg);
|
||||
} else {
|
||||
// Multi-actor over
|
||||
forceWidth = Math.abs(startx - stopx) + conf.actorMargin
|
||||
drawNote(diagram, (startx + stopx + conf.width - forceWidth) / 2, bounds.getVerticalPos(), msg,
|
||||
forceWidth)
|
||||
forceWidth = Math.abs(startx - stopx) + conf.actorMargin;
|
||||
drawNote(
|
||||
diagram,
|
||||
(startx + stopx + conf.width - forceWidth) / 2,
|
||||
bounds.getVerticalPos(),
|
||||
msg,
|
||||
forceWidth
|
||||
);
|
||||
}
|
||||
break
|
||||
break;
|
||||
case parser.yy.LINETYPE.ACTIVE_START:
|
||||
bounds.newActivation(msg, diagram)
|
||||
break
|
||||
bounds.newActivation(msg, diagram);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ACTIVE_END:
|
||||
activeEnd(msg, bounds.getVerticalPos())
|
||||
break
|
||||
activeEnd(msg, bounds.getVerticalPos());
|
||||
break;
|
||||
case parser.yy.LINETYPE.LOOP_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
bounds.newLoop(msg.message)
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin)
|
||||
break
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.LOOP_END:
|
||||
loopData = bounds.endLoop()
|
||||
loopData = bounds.endLoop();
|
||||
|
||||
svgDraw.drawLoop(diagram, loopData, 'loop', conf)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
svgDraw.drawLoop(diagram, loopData, 'loop', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.RECT_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
bounds.newLoop(undefined, msg.message)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(undefined, msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.RECT_END:
|
||||
const rectData = bounds.endLoop()
|
||||
svgDraw.drawBackgroundRect(diagram, rectData)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
const rectData = bounds.endLoop();
|
||||
svgDraw.drawBackgroundRect(diagram, rectData);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.OPT_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
bounds.newLoop(msg.message)
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin)
|
||||
break
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.OPT_END:
|
||||
loopData = bounds.endLoop()
|
||||
loopData = bounds.endLoop();
|
||||
|
||||
svgDraw.drawLoop(diagram, loopData, 'opt', conf)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
svgDraw.drawLoop(diagram, loopData, 'opt', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
bounds.newLoop(msg.message)
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin)
|
||||
break
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_ELSE:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
loopData = bounds.addSectionToLoop(msg.message)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopData = bounds.addSectionToLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.ALT_END:
|
||||
loopData = bounds.endLoop()
|
||||
loopData = bounds.endLoop();
|
||||
|
||||
svgDraw.drawLoop(diagram, loopData, 'alt', conf)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
svgDraw.drawLoop(diagram, loopData, 'alt', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_START:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
bounds.newLoop(msg.message)
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin)
|
||||
break
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
bounds.newLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin + conf.boxTextMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_AND:
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
loopData = bounds.addSectionToLoop(msg.message)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
loopData = bounds.addSectionToLoop(msg.message);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
case parser.yy.LINETYPE.PAR_END:
|
||||
loopData = bounds.endLoop()
|
||||
svgDraw.drawLoop(diagram, loopData, 'par', conf)
|
||||
bounds.bumpVerticalPos(conf.boxMargin)
|
||||
break
|
||||
loopData = bounds.endLoop();
|
||||
svgDraw.drawLoop(diagram, loopData, 'par', conf);
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
break;
|
||||
default:
|
||||
try {
|
||||
// lastMsg = msg
|
||||
bounds.bumpVerticalPos(conf.messageMargin)
|
||||
const fromBounds = actorFlowVerticaBounds(msg.from)
|
||||
const toBounds = actorFlowVerticaBounds(msg.to)
|
||||
const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0
|
||||
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1
|
||||
startx = fromBounds[fromIdx]
|
||||
stopx = toBounds[toIdx]
|
||||
bounds.bumpVerticalPos(conf.messageMargin);
|
||||
const fromBounds = actorFlowVerticaBounds(msg.from);
|
||||
const toBounds = actorFlowVerticaBounds(msg.to);
|
||||
const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0;
|
||||
const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1;
|
||||
startx = fromBounds[fromIdx];
|
||||
stopx = toBounds[toIdx];
|
||||
|
||||
const verticalPos = bounds.getVerticalPos()
|
||||
drawMessage(diagram, startx, stopx, verticalPos, msg, sequenceIndex)
|
||||
const allBounds = fromBounds.concat(toBounds)
|
||||
bounds.insert(Math.min.apply(null, allBounds), verticalPos, Math.max.apply(null, allBounds), verticalPos)
|
||||
const verticalPos = bounds.getVerticalPos();
|
||||
drawMessage(diagram, startx, stopx, verticalPos, msg, sequenceIndex);
|
||||
const allBounds = fromBounds.concat(toBounds);
|
||||
bounds.insert(
|
||||
Math.min.apply(null, allBounds),
|
||||
verticalPos,
|
||||
Math.max.apply(null, allBounds),
|
||||
verticalPos
|
||||
);
|
||||
} catch (e) {
|
||||
logger.error('error while drawing message', e)
|
||||
logger.error('error while drawing message', e);
|
||||
}
|
||||
}
|
||||
// Increment sequence counter if msg.type is a line (and not another event like activation or note, etc)
|
||||
if ([
|
||||
parser.yy.LINETYPE.SOLID_OPEN,
|
||||
parser.yy.LINETYPE.DOTTED_OPEN,
|
||||
parser.yy.LINETYPE.SOLID,
|
||||
parser.yy.LINETYPE.DOTTED,
|
||||
parser.yy.LINETYPE.SOLID_CROSS,
|
||||
parser.yy.LINETYPE.DOTTED_CROSS
|
||||
].includes(msg.type)) {
|
||||
sequenceIndex++
|
||||
if (
|
||||
[
|
||||
parser.yy.LINETYPE.SOLID_OPEN,
|
||||
parser.yy.LINETYPE.DOTTED_OPEN,
|
||||
parser.yy.LINETYPE.SOLID,
|
||||
parser.yy.LINETYPE.DOTTED,
|
||||
parser.yy.LINETYPE.SOLID_CROSS,
|
||||
parser.yy.LINETYPE.DOTTED_CROSS
|
||||
].includes(msg.type)
|
||||
) {
|
||||
sequenceIndex++;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (conf.mirrorActors) {
|
||||
// Draw actors below diagram
|
||||
bounds.bumpVerticalPos(conf.boxMargin * 2)
|
||||
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos())
|
||||
bounds.bumpVerticalPos(conf.boxMargin * 2);
|
||||
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos());
|
||||
}
|
||||
|
||||
const box = bounds.getBounds()
|
||||
const box = bounds.getBounds();
|
||||
|
||||
// Adjust line height of actor lines now that the height of the diagram is known
|
||||
logger.debug('For line height fix Querying: #' + id + ' .actor-line')
|
||||
const actorLines = d3.selectAll('#' + id + ' .actor-line')
|
||||
actorLines.attr('y2', box.stopy)
|
||||
logger.debug('For line height fix Querying: #' + id + ' .actor-line');
|
||||
const actorLines = d3.selectAll('#' + id + ' .actor-line');
|
||||
actorLines.attr('y2', box.stopy);
|
||||
|
||||
let height = box.stopy - box.starty + 2 * conf.diagramMarginY
|
||||
let height = box.stopy - box.starty + 2 * conf.diagramMarginY;
|
||||
if (conf.mirrorActors) {
|
||||
height = height - conf.boxMargin + conf.bottomMarginAdj
|
||||
height = height - conf.boxMargin + conf.bottomMarginAdj;
|
||||
}
|
||||
|
||||
const width = (box.stopx - box.startx) + (2 * conf.diagramMarginX)
|
||||
const width = box.stopx - box.startx + 2 * conf.diagramMarginX;
|
||||
|
||||
if (title) {
|
||||
diagram.append('text')
|
||||
diagram
|
||||
.append('text')
|
||||
.text(title)
|
||||
.attr('x', ((box.stopx - box.startx) / 2) - (2 * conf.diagramMarginX))
|
||||
.attr('y', -25)
|
||||
.attr('x', (box.stopx - box.startx) / 2 - 2 * conf.diagramMarginX)
|
||||
.attr('y', -25);
|
||||
}
|
||||
|
||||
if (conf.useMaxWidth) {
|
||||
diagram.attr('height', '100%')
|
||||
diagram.attr('width', '100%')
|
||||
diagram.attr('style', 'max-width:' + (width) + 'px;')
|
||||
diagram.attr('height', '100%');
|
||||
diagram.attr('width', '100%');
|
||||
diagram.attr('style', 'max-width:' + width + 'px;');
|
||||
} else {
|
||||
diagram.attr('height', height)
|
||||
diagram.attr('width', width)
|
||||
diagram.attr('height', height);
|
||||
diagram.attr('width', width);
|
||||
}
|
||||
const extraVertForTitle = title ? 40 : 0
|
||||
diagram.attr('viewBox', (box.startx - conf.diagramMarginX) + ' -' + (conf.diagramMarginY + extraVertForTitle) + ' ' + width + ' ' + (height + extraVertForTitle))
|
||||
}
|
||||
const extraVertForTitle = title ? 40 : 0;
|
||||
diagram.attr(
|
||||
'viewBox',
|
||||
box.startx -
|
||||
conf.diagramMarginX +
|
||||
' -' +
|
||||
(conf.diagramMarginY + extraVertForTitle) +
|
||||
' ' +
|
||||
width +
|
||||
' ' +
|
||||
(height + extraVertForTitle)
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
bounds,
|
||||
drawActors,
|
||||
setConf,
|
||||
draw
|
||||
}
|
||||
};
|
||||
|
@ -1,71 +1,87 @@
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const 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)
|
||||
export const drawRect = function(elem, rectData) {
|
||||
const 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)
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem
|
||||
}
|
||||
return rectElem;
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData, width) {
|
||||
export const drawText = function(elem, textData, width) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br\/?>/ig, ' ')
|
||||
const nText = textData.text.replace(/<br\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text')
|
||||
textElem.attr('x', textData.x)
|
||||
textElem.attr('y', textData.y)
|
||||
textElem.style('text-anchor', textData.anchor)
|
||||
textElem.attr('fill', textData.fill)
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
textElem.attr('fill', textData.fill);
|
||||
if (typeof textData.class !== 'undefined') {
|
||||
textElem.attr('class', textData.class)
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan')
|
||||
span.attr('x', textData.x + textData.textMargin * 2)
|
||||
span.attr('fill', textData.fill)
|
||||
span.text(nText)
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.attr('fill', textData.fill);
|
||||
span.text(nText);
|
||||
|
||||
return textElem
|
||||
}
|
||||
return textElem;
|
||||
};
|
||||
|
||||
export const drawLabel = function (elem, txtObject) {
|
||||
function genPoints (x, y, width, height, cut) {
|
||||
return x + ',' + y + ' ' +
|
||||
(x + width) + ',' + y + ' ' +
|
||||
(x + width) + ',' + (y + height - cut) + ' ' +
|
||||
(x + width - cut * 1.2) + ',' + (y + height) + ' ' +
|
||||
(x) + ',' + (y + height)
|
||||
export const drawLabel = function(elem, txtObject) {
|
||||
function genPoints(x, y, width, height, cut) {
|
||||
return (
|
||||
x +
|
||||
',' +
|
||||
y +
|
||||
' ' +
|
||||
(x + width) +
|
||||
',' +
|
||||
y +
|
||||
' ' +
|
||||
(x + width) +
|
||||
',' +
|
||||
(y + height - cut) +
|
||||
' ' +
|
||||
(x + width - cut * 1.2) +
|
||||
',' +
|
||||
(y + height) +
|
||||
' ' +
|
||||
x +
|
||||
',' +
|
||||
(y + height)
|
||||
);
|
||||
}
|
||||
const polygon = elem.append('polygon')
|
||||
polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7))
|
||||
polygon.attr('class', 'labelBox')
|
||||
const polygon = elem.append('polygon');
|
||||
polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7));
|
||||
polygon.attr('class', 'labelBox');
|
||||
|
||||
txtObject.y = txtObject.y + txtObject.labelMargin
|
||||
txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin
|
||||
drawText(elem, txtObject)
|
||||
}
|
||||
txtObject.y = txtObject.y + txtObject.labelMargin;
|
||||
txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin;
|
||||
drawText(elem, txtObject);
|
||||
};
|
||||
|
||||
let actorCnt = -1
|
||||
let actorCnt = -1;
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* @param center - The center of the the actor
|
||||
* @param pos The position if the actor in the liost of actors
|
||||
* @param description The text in the box
|
||||
*/
|
||||
export const drawActor = function (elem, left, verticalPos, description, conf) {
|
||||
const center = left + (conf.width / 2)
|
||||
const g = elem.append('g')
|
||||
export const drawActor = function(elem, left, verticalPos, description, conf) {
|
||||
const center = left + conf.width / 2;
|
||||
const g = elem.append('g');
|
||||
if (verticalPos === 0) {
|
||||
actorCnt++
|
||||
actorCnt++;
|
||||
g.append('line')
|
||||
.attr('id', 'actor' + actorCnt)
|
||||
.attr('x1', center)
|
||||
@ -74,43 +90,51 @@ export const drawActor = function (elem, left, verticalPos, description, conf) {
|
||||
.attr('y2', 2000)
|
||||
.attr('class', 'actor-line')
|
||||
.attr('stroke-width', '0.5px')
|
||||
.attr('stroke', '#999')
|
||||
.attr('stroke', '#999');
|
||||
}
|
||||
|
||||
const rect = getNoteRect()
|
||||
rect.x = left
|
||||
rect.y = verticalPos
|
||||
rect.fill = '#eaeaea'
|
||||
rect.width = conf.width
|
||||
rect.height = conf.height
|
||||
rect.class = 'actor'
|
||||
rect.rx = 3
|
||||
rect.ry = 3
|
||||
drawRect(g, rect)
|
||||
const rect = getNoteRect();
|
||||
rect.x = left;
|
||||
rect.y = verticalPos;
|
||||
rect.fill = '#eaeaea';
|
||||
rect.width = conf.width;
|
||||
rect.height = conf.height;
|
||||
rect.class = 'actor';
|
||||
rect.rx = 3;
|
||||
rect.ry = 3;
|
||||
drawRect(g, rect);
|
||||
|
||||
_drawTextCandidateFunc(conf)(description, g,
|
||||
rect.x, rect.y, rect.width, rect.height, { 'class': 'actor' }, conf)
|
||||
}
|
||||
_drawTextCandidateFunc(conf)(
|
||||
description,
|
||||
g,
|
||||
rect.x,
|
||||
rect.y,
|
||||
rect.width,
|
||||
rect.height,
|
||||
{ class: 'actor' },
|
||||
conf
|
||||
);
|
||||
};
|
||||
|
||||
export const anchorElement = function (elem) {
|
||||
return elem.append('g')
|
||||
}
|
||||
export const anchorElement = function(elem) {
|
||||
return elem.append('g');
|
||||
};
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
* @param elem - element to append activation rect
|
||||
* @param bounds - activation box bounds
|
||||
* @param verticalPos - precise y cooridnate of bottom activation box edge
|
||||
*/
|
||||
export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) {
|
||||
const rect = getNoteRect()
|
||||
const g = bounds.anchored
|
||||
rect.x = bounds.startx
|
||||
rect.y = bounds.starty
|
||||
rect.class = 'activation' + (actorActivations % 3) // Will evaluate to 0, 1 or 2
|
||||
rect.width = bounds.stopx - bounds.startx
|
||||
rect.height = verticalPos - bounds.starty
|
||||
drawRect(g, rect)
|
||||
}
|
||||
export const drawActivation = function(elem, bounds, verticalPos, conf, actorActivations) {
|
||||
const rect = getNoteRect();
|
||||
const g = bounds.anchored;
|
||||
rect.x = bounds.startx;
|
||||
rect.y = bounds.starty;
|
||||
rect.class = 'activation' + (actorActivations % 3); // Will evaluate to 0, 1 or 2
|
||||
rect.width = bounds.stopx - bounds.startx;
|
||||
rect.height = verticalPos - bounds.starty;
|
||||
drawRect(g, rect);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an actor in the diagram with the attaced line
|
||||
@ -118,60 +142,61 @@ export const drawActivation = function (elem, bounds, verticalPos, conf, actorAc
|
||||
* @param pos The position if the actor in the list of actors
|
||||
* @param description The text in the box
|
||||
*/
|
||||
export const drawLoop = function (elem, bounds, labelText, conf) {
|
||||
const g = elem.append('g')
|
||||
const drawLoopLine = function (startx, starty, stopx, stopy) {
|
||||
return g.append('line')
|
||||
export const drawLoop = function(elem, bounds, labelText, conf) {
|
||||
const g = elem.append('g');
|
||||
const drawLoopLine = function(startx, starty, stopx, stopy) {
|
||||
return g
|
||||
.append('line')
|
||||
.attr('x1', startx)
|
||||
.attr('y1', starty)
|
||||
.attr('x2', stopx)
|
||||
.attr('y2', stopy)
|
||||
.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)
|
||||
.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.sections !== 'undefined') {
|
||||
bounds.sections.forEach(function (item) {
|
||||
drawLoopLine(bounds.startx, item, bounds.stopx, item).style('stroke-dasharray', '3, 3')
|
||||
})
|
||||
bounds.sections.forEach(function(item) {
|
||||
drawLoopLine(bounds.startx, item, bounds.stopx, item).style('stroke-dasharray', '3, 3');
|
||||
});
|
||||
}
|
||||
|
||||
let txt = getTextObj()
|
||||
txt.text = labelText
|
||||
txt.x = bounds.startx
|
||||
txt.y = bounds.starty
|
||||
txt.labelMargin = 1.5 * 10 // This is the small box that says "loop"
|
||||
txt.class = 'labelText' // Its size & position are fixed.
|
||||
let txt = getTextObj();
|
||||
txt.text = labelText;
|
||||
txt.x = bounds.startx;
|
||||
txt.y = bounds.starty;
|
||||
txt.labelMargin = 1.5 * 10; // This is the small box that says "loop"
|
||||
txt.class = 'labelText'; // Its size & position are fixed.
|
||||
|
||||
drawLabel(g, txt)
|
||||
drawLabel(g, txt);
|
||||
|
||||
txt = 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'
|
||||
txt = 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';
|
||||
|
||||
drawText(g, txt)
|
||||
drawText(g, txt);
|
||||
|
||||
if (typeof bounds.sectionTitles !== 'undefined') {
|
||||
bounds.sectionTitles.forEach(function (item, idx) {
|
||||
bounds.sectionTitles.forEach(function(item, idx) {
|
||||
if (item !== '') {
|
||||
txt.text = '[ ' + item + ' ]'
|
||||
txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin
|
||||
drawText(g, txt)
|
||||
txt.text = '[ ' + item + ' ]';
|
||||
txt.y = bounds.sections[idx] + 1.5 * conf.boxMargin;
|
||||
drawText(g, txt);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a background rectangle
|
||||
* @param color - The fill color for the background
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
export const drawBackgroundRect = function(elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
@ -179,14 +204,16 @@ export const drawBackgroundRect = function (elem, bounds) {
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
class: 'rect'
|
||||
})
|
||||
rectElem.lower()
|
||||
}
|
||||
});
|
||||
rectElem.lower();
|
||||
};
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*/
|
||||
export const insertArrowHead = function (elem) {
|
||||
elem.append('defs').append('marker')
|
||||
export const insertArrowHead = function(elem) {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('refX', 5)
|
||||
.attr('refY', 2)
|
||||
@ -194,13 +221,15 @@ export const insertArrowHead = function (elem) {
|
||||
.attr('markerHeight', 4)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 0,0 V 4 L6,2 Z') // this is actual shape for arrowhead
|
||||
}
|
||||
.attr('d', 'M 0,0 V 4 L6,2 Z'); // this is actual shape for arrowhead
|
||||
};
|
||||
/**
|
||||
* Setup node number. The result is appended to the svg.
|
||||
*/
|
||||
export const insertSequenceNumber = function (elem) {
|
||||
elem.append('defs').append('marker')
|
||||
export const insertSequenceNumber = function(elem) {
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', 'sequencenumber')
|
||||
.attr('refX', 15)
|
||||
.attr('refY', 15)
|
||||
@ -210,45 +239,48 @@ export const insertSequenceNumber = function (elem) {
|
||||
.append('circle')
|
||||
.attr('cx', 15)
|
||||
.attr('cy', 15)
|
||||
.attr('r', 6)
|
||||
// .style("fill", '#f00');
|
||||
}
|
||||
.attr('r', 6);
|
||||
// .style("fill", '#f00');
|
||||
};
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*/
|
||||
export const insertArrowCrossHead = function (elem) {
|
||||
const defs = elem.append('defs')
|
||||
const marker = defs.append('marker')
|
||||
export const insertArrowCrossHead = function(elem) {
|
||||
const defs = elem.append('defs');
|
||||
const marker = defs
|
||||
.append('marker')
|
||||
.attr('id', 'crosshead')
|
||||
.attr('markerWidth', 15)
|
||||
.attr('markerHeight', 8)
|
||||
.attr('orient', 'auto')
|
||||
.attr('refX', 16)
|
||||
.attr('refY', 4)
|
||||
.attr('refY', 4);
|
||||
|
||||
// The arrow
|
||||
marker.append('path')
|
||||
marker
|
||||
.append('path')
|
||||
.attr('fill', 'black')
|
||||
.attr('stroke', '#000000')
|
||||
.style('stroke-dasharray', ('0, 0'))
|
||||
.style('stroke-dasharray', '0, 0')
|
||||
.attr('stroke-width', '1px')
|
||||
.attr('d', 'M 9,2 V 6 L16,4 Z')
|
||||
.attr('d', 'M 9,2 V 6 L16,4 Z');
|
||||
|
||||
// The cross
|
||||
marker.append('path')
|
||||
marker
|
||||
.append('path')
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#000000')
|
||||
.style('stroke-dasharray', ('0, 0'))
|
||||
.style('stroke-dasharray', '0, 0')
|
||||
.attr('stroke-width', '1px')
|
||||
.attr('d', 'M 0,1 L 6,7 M 6,1 L 0,7')
|
||||
.attr('d', 'M 0,1 L 6,7 M 6,1 L 0,7');
|
||||
// this is actual shape for arrowhead
|
||||
}
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
export const getTextObj = function() {
|
||||
const txt = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
'fill': undefined,
|
||||
fill: undefined,
|
||||
'text-anchor': 'start',
|
||||
style: '#666',
|
||||
width: 100,
|
||||
@ -256,11 +288,11 @@ export const getTextObj = function () {
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0
|
||||
}
|
||||
return txt
|
||||
}
|
||||
};
|
||||
return txt;
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
export const getNoteRect = function() {
|
||||
const rect = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@ -271,72 +303,87 @@ export const getNoteRect = function () {
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0
|
||||
}
|
||||
return rect
|
||||
}
|
||||
};
|
||||
return rect;
|
||||
};
|
||||
|
||||
const _drawTextCandidateFunc = (function () {
|
||||
function byText (content, g, x, y, width, height, textAttrs) {
|
||||
const text = g.append('text')
|
||||
.attr('x', x + width / 2).attr('y', y + height / 2 + 5)
|
||||
const _drawTextCandidateFunc = (function() {
|
||||
function byText(content, g, x, y, width, height, textAttrs) {
|
||||
const text = g
|
||||
.append('text')
|
||||
.attr('x', x + width / 2)
|
||||
.attr('y', y + height / 2 + 5)
|
||||
.style('text-anchor', 'middle')
|
||||
.text(content)
|
||||
_setTextAttrs(text, textAttrs)
|
||||
.text(content);
|
||||
_setTextAttrs(text, textAttrs);
|
||||
}
|
||||
|
||||
function byTspan (content, g, x, y, width, height, textAttrs, conf) {
|
||||
const { actorFontSize, actorFontFamily } = conf
|
||||
function byTspan(content, g, x, y, width, height, textAttrs, conf) {
|
||||
const { actorFontSize, actorFontFamily } = conf;
|
||||
|
||||
const lines = content.split(/<br\/?>/ig)
|
||||
const lines = content.split(/<br\/?>/gi);
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const dy = (i * actorFontSize) - (actorFontSize * (lines.length - 1) / 2)
|
||||
const text = g.append('text')
|
||||
.attr('x', x + width / 2).attr('y', y)
|
||||
const dy = i * actorFontSize - (actorFontSize * (lines.length - 1)) / 2;
|
||||
const text = g
|
||||
.append('text')
|
||||
.attr('x', x + width / 2)
|
||||
.attr('y', y)
|
||||
.style('text-anchor', 'middle')
|
||||
.style('font-size', actorFontSize)
|
||||
.style('font-family', actorFontFamily)
|
||||
text.append('tspan')
|
||||
.attr('x', x + width / 2).attr('dy', dy)
|
||||
.text(lines[i])
|
||||
.style('font-family', actorFontFamily);
|
||||
text
|
||||
.append('tspan')
|
||||
.attr('x', x + width / 2)
|
||||
.attr('dy', dy)
|
||||
.text(lines[i]);
|
||||
|
||||
text.attr('y', y + height / 2.0)
|
||||
text
|
||||
.attr('y', y + height / 2.0)
|
||||
.attr('dominant-baseline', 'central')
|
||||
.attr('alignment-baseline', 'central')
|
||||
.attr('alignment-baseline', 'central');
|
||||
|
||||
_setTextAttrs(text, textAttrs)
|
||||
_setTextAttrs(text, textAttrs);
|
||||
}
|
||||
}
|
||||
|
||||
function byFo (content, g, x, y, width, height, textAttrs, conf) {
|
||||
const s = g.append('switch')
|
||||
const f = s.append('foreignObject')
|
||||
.attr('x', x).attr('y', y)
|
||||
.attr('width', width).attr('height', height)
|
||||
function byFo(content, g, x, y, width, height, textAttrs, conf) {
|
||||
const s = g.append('switch');
|
||||
const f = s
|
||||
.append('foreignObject')
|
||||
.attr('x', x)
|
||||
.attr('y', y)
|
||||
.attr('width', width)
|
||||
.attr('height', height);
|
||||
|
||||
const text = f.append('div').style('display', 'table')
|
||||
.style('height', '100%').style('width', '100%')
|
||||
const text = f
|
||||
.append('div')
|
||||
.style('display', 'table')
|
||||
.style('height', '100%')
|
||||
.style('width', '100%');
|
||||
|
||||
text.append('div').style('display', 'table-cell')
|
||||
.style('text-align', 'center').style('vertical-align', 'middle')
|
||||
.text(content)
|
||||
text
|
||||
.append('div')
|
||||
.style('display', 'table-cell')
|
||||
.style('text-align', 'center')
|
||||
.style('vertical-align', 'middle')
|
||||
.text(content);
|
||||
|
||||
byTspan(content, s, x, y, width, height, textAttrs, conf)
|
||||
_setTextAttrs(text, textAttrs)
|
||||
byTspan(content, s, x, y, width, height, textAttrs, conf);
|
||||
_setTextAttrs(text, textAttrs);
|
||||
}
|
||||
|
||||
function _setTextAttrs (toText, fromTextAttrsDict) {
|
||||
function _setTextAttrs(toText, fromTextAttrsDict) {
|
||||
for (const key in fromTextAttrsDict) {
|
||||
if (fromTextAttrsDict.hasOwnProperty(key)) {
|
||||
toText.attr(key, fromTextAttrsDict[key])
|
||||
toText.attr(key, fromTextAttrsDict[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return function (conf) {
|
||||
return conf.textPlacement === 'fo' ? byFo : (
|
||||
conf.textPlacement === 'old' ? byText : byTspan)
|
||||
}
|
||||
})()
|
||||
return function(conf) {
|
||||
return conf.textPlacement === 'fo' ? byFo : conf.textPlacement === 'old' ? byText : byTspan;
|
||||
};
|
||||
})();
|
||||
|
||||
export default {
|
||||
drawRect,
|
||||
@ -352,4 +399,4 @@ export default {
|
||||
insertArrowCrossHead,
|
||||
getTextObj,
|
||||
getNoteRect
|
||||
}
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
/* eslint-env jasmine */
|
||||
const svgDraw = require('./svgDraw')
|
||||
const { MockD3 } = require('d3')
|
||||
const svgDraw = require('./svgDraw');
|
||||
const { MockD3 } = require('d3');
|
||||
|
||||
describe('svgDraw', function () {
|
||||
describe('drawRect', function () {
|
||||
it('it should append a rectangle', function () {
|
||||
const svg = MockD3('svg')
|
||||
describe('svgDraw', function() {
|
||||
describe('drawRect', function() {
|
||||
it('it should append a rectangle', function() {
|
||||
const svg = MockD3('svg');
|
||||
svgDraw.drawRect(svg, {
|
||||
x: 10,
|
||||
y: 10,
|
||||
@ -16,22 +16,22 @@ describe('svgDraw', function () {
|
||||
rx: '10',
|
||||
ry: '10',
|
||||
class: 'unitTestRectangleClass'
|
||||
})
|
||||
expect(svg.__children.length).toBe(1)
|
||||
const rect = svg.__children[0]
|
||||
expect(rect.__name).toBe('rect')
|
||||
expect(rect.attr).toHaveBeenCalledWith('x', 10)
|
||||
expect(rect.attr).toHaveBeenCalledWith('y', 10)
|
||||
expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc')
|
||||
expect(rect.attr).toHaveBeenCalledWith('stroke', 'red')
|
||||
expect(rect.attr).toHaveBeenCalledWith('width', '20')
|
||||
expect(rect.attr).toHaveBeenCalledWith('height', '20')
|
||||
expect(rect.attr).toHaveBeenCalledWith('rx', '10')
|
||||
expect(rect.attr).toHaveBeenCalledWith('ry', '10')
|
||||
expect(rect.attr).toHaveBeenCalledWith('class', 'unitTestRectangleClass')
|
||||
})
|
||||
});
|
||||
expect(svg.__children.length).toBe(1);
|
||||
const rect = svg.__children[0];
|
||||
expect(rect.__name).toBe('rect');
|
||||
expect(rect.attr).toHaveBeenCalledWith('x', 10);
|
||||
expect(rect.attr).toHaveBeenCalledWith('y', 10);
|
||||
expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc');
|
||||
expect(rect.attr).toHaveBeenCalledWith('stroke', 'red');
|
||||
expect(rect.attr).toHaveBeenCalledWith('width', '20');
|
||||
expect(rect.attr).toHaveBeenCalledWith('height', '20');
|
||||
expect(rect.attr).toHaveBeenCalledWith('rx', '10');
|
||||
expect(rect.attr).toHaveBeenCalledWith('ry', '10');
|
||||
expect(rect.attr).toHaveBeenCalledWith('class', 'unitTestRectangleClass');
|
||||
});
|
||||
it('it should not add the class attribute if a class isn`t provided', () => {
|
||||
const svg = MockD3('svg')
|
||||
const svg = MockD3('svg');
|
||||
svgDraw.drawRect(svg, {
|
||||
x: 10,
|
||||
y: 10,
|
||||
@ -41,17 +41,17 @@ describe('svgDraw', function () {
|
||||
height: '20',
|
||||
rx: '10',
|
||||
ry: '10'
|
||||
})
|
||||
expect(svg.__children.length).toBe(1)
|
||||
const rect = svg.__children[0]
|
||||
expect(rect.__name).toBe('rect')
|
||||
expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc')
|
||||
expect(rect.attr).not.toHaveBeenCalledWith('class', expect.anything())
|
||||
})
|
||||
})
|
||||
describe('drawBackgroundRect', function () {
|
||||
it('it should append a rect before the previous element within a given bound', function () {
|
||||
const svg = MockD3('svg')
|
||||
});
|
||||
expect(svg.__children.length).toBe(1);
|
||||
const rect = svg.__children[0];
|
||||
expect(rect.__name).toBe('rect');
|
||||
expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc');
|
||||
expect(rect.attr).not.toHaveBeenCalledWith('class', expect.anything());
|
||||
});
|
||||
});
|
||||
describe('drawBackgroundRect', function() {
|
||||
it('it should append a rect before the previous element within a given bound', function() {
|
||||
const svg = MockD3('svg');
|
||||
const boundingRect = {
|
||||
startx: 50,
|
||||
starty: 200,
|
||||
@ -59,18 +59,18 @@ describe('svgDraw', function () {
|
||||
stopy: 260,
|
||||
title: undefined,
|
||||
fill: '#ccc'
|
||||
}
|
||||
svgDraw.drawBackgroundRect(svg, boundingRect)
|
||||
expect(svg.__children.length).toBe(1)
|
||||
const rect = svg.__children[0]
|
||||
expect(rect.__name).toBe('rect')
|
||||
expect(rect.attr).toHaveBeenCalledWith('x', 50)
|
||||
expect(rect.attr).toHaveBeenCalledWith('y', 200)
|
||||
expect(rect.attr).toHaveBeenCalledWith('width', 100)
|
||||
expect(rect.attr).toHaveBeenCalledWith('height', 60)
|
||||
expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc')
|
||||
expect(rect.attr).toHaveBeenCalledWith('class', 'rect')
|
||||
expect(rect.lower).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
};
|
||||
svgDraw.drawBackgroundRect(svg, boundingRect);
|
||||
expect(svg.__children.length).toBe(1);
|
||||
const rect = svg.__children[0];
|
||||
expect(rect.__name).toBe('rect');
|
||||
expect(rect.attr).toHaveBeenCalledWith('x', 50);
|
||||
expect(rect.attr).toHaveBeenCalledWith('y', 200);
|
||||
expect(rect.attr).toHaveBeenCalledWith('width', 100);
|
||||
expect(rect.attr).toHaveBeenCalledWith('height', 60);
|
||||
expect(rect.attr).toHaveBeenCalledWith('fill', '#ccc');
|
||||
expect(rect.attr).toHaveBeenCalledWith('class', 'rect');
|
||||
expect(rect.lower).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import moment from 'moment-mini'
|
||||
import moment from 'moment-mini';
|
||||
|
||||
export const LEVELS = {
|
||||
debug: 1,
|
||||
@ -6,7 +6,7 @@ export const LEVELS = {
|
||||
warn: 3,
|
||||
error: 4,
|
||||
fatal: 5
|
||||
}
|
||||
};
|
||||
|
||||
export const logger = {
|
||||
debug: () => {},
|
||||
@ -14,32 +14,32 @@ export const logger = {
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
fatal: () => {}
|
||||
}
|
||||
};
|
||||
|
||||
export const setLogLevel = function (level) {
|
||||
logger.debug = () => {}
|
||||
logger.info = () => {}
|
||||
logger.warn = () => {}
|
||||
logger.error = () => {}
|
||||
logger.fatal = () => {}
|
||||
export const setLogLevel = function(level) {
|
||||
logger.debug = () => {};
|
||||
logger.info = () => {};
|
||||
logger.warn = () => {};
|
||||
logger.error = () => {};
|
||||
logger.fatal = () => {};
|
||||
if (level <= LEVELS.fatal) {
|
||||
logger.fatal = console.log.bind(console, '\x1b[35m', format('FATAL'))
|
||||
logger.fatal = console.log.bind(console, '\x1b[35m', format('FATAL'));
|
||||
}
|
||||
if (level <= LEVELS.error) {
|
||||
logger.error = console.log.bind(console, '\x1b[31m', format('ERROR'))
|
||||
logger.error = console.log.bind(console, '\x1b[31m', format('ERROR'));
|
||||
}
|
||||
if (level <= LEVELS.warn) {
|
||||
logger.warn = console.log.bind(console, `\x1b[33m`, format('WARN'))
|
||||
logger.warn = console.log.bind(console, `\x1b[33m`, format('WARN'));
|
||||
}
|
||||
if (level <= LEVELS.info) {
|
||||
logger.info = console.log.bind(console, '\x1b[34m', format('INFO'))
|
||||
logger.info = console.log.bind(console, '\x1b[34m', format('INFO'));
|
||||
}
|
||||
if (level <= LEVELS.debug) {
|
||||
logger.debug = console.log.bind(console, '\x1b[32m', format('DEBUG'))
|
||||
logger.debug = console.log.bind(console, '\x1b[32m', format('DEBUG'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const format = (level) => {
|
||||
const time = moment().format('HH:mm:ss.SSS')
|
||||
return `${time} : ${level} : `
|
||||
}
|
||||
const format = level => {
|
||||
const time = moment().format('HH:mm:ss.SSS');
|
||||
return `${time} : ${level} : `;
|
||||
};
|
||||
|
132
src/mermaid.js
132
src/mermaid.js
@ -2,10 +2,10 @@
|
||||
* Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid functionality and to render
|
||||
* the diagrams to svg code.
|
||||
*/
|
||||
import he from 'he'
|
||||
import he from 'he';
|
||||
|
||||
import mermaidAPI from './mermaidAPI'
|
||||
import { logger } from './logger'
|
||||
import mermaidAPI from './mermaidAPI';
|
||||
import { logger } from './logger';
|
||||
|
||||
/**
|
||||
* ## init
|
||||
@ -28,126 +28,142 @@ import { logger } from './logger'
|
||||
* Renders the mermaid diagrams
|
||||
* @param nodes a css selector or an array of nodes
|
||||
*/
|
||||
const init = function () {
|
||||
const conf = mermaidAPI.getConfig()
|
||||
logger.debug('Starting rendering diagrams')
|
||||
let nodes
|
||||
const init = function() {
|
||||
const conf = mermaidAPI.getConfig();
|
||||
logger.debug('Starting rendering diagrams');
|
||||
let nodes;
|
||||
if (arguments.length >= 2) {
|
||||
/*! sequence config was passed as #1 */
|
||||
if (typeof arguments[0] !== 'undefined') {
|
||||
mermaid.sequenceConfig = arguments[0]
|
||||
mermaid.sequenceConfig = arguments[0];
|
||||
}
|
||||
|
||||
nodes = arguments[1]
|
||||
nodes = arguments[1];
|
||||
} else {
|
||||
nodes = arguments[0]
|
||||
nodes = arguments[0];
|
||||
}
|
||||
|
||||
// if last argument is a function this is the callback function
|
||||
let callback
|
||||
let callback;
|
||||
if (typeof arguments[arguments.length - 1] === 'function') {
|
||||
callback = arguments[arguments.length - 1]
|
||||
logger.debug('Callback function found')
|
||||
callback = arguments[arguments.length - 1];
|
||||
logger.debug('Callback function found');
|
||||
} else {
|
||||
if (typeof conf.mermaid !== 'undefined') {
|
||||
if (typeof conf.mermaid.callback === 'function') {
|
||||
callback = conf.mermaid.callback
|
||||
logger.debug('Callback function found')
|
||||
callback = conf.mermaid.callback;
|
||||
logger.debug('Callback function found');
|
||||
} else {
|
||||
logger.debug('No Callback function found')
|
||||
logger.debug('No Callback function found');
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes = nodes === undefined ? document.querySelectorAll('.mermaid')
|
||||
: typeof nodes === 'string' ? document.querySelectorAll(nodes)
|
||||
: nodes instanceof window.Node ? [nodes]
|
||||
: nodes // Last case - sequence config was passed pick next
|
||||
nodes =
|
||||
nodes === undefined
|
||||
? document.querySelectorAll('.mermaid')
|
||||
: typeof nodes === 'string'
|
||||
? document.querySelectorAll(nodes)
|
||||
: nodes instanceof window.Node
|
||||
? [nodes]
|
||||
: nodes; // Last case - sequence config was passed pick next
|
||||
|
||||
logger.debug('Start On Load before: ' + mermaid.startOnLoad)
|
||||
logger.debug('Start On Load before: ' + mermaid.startOnLoad);
|
||||
if (typeof mermaid.startOnLoad !== 'undefined') {
|
||||
logger.debug('Start On Load inner: ' + mermaid.startOnLoad)
|
||||
mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad })
|
||||
logger.debug('Start On Load inner: ' + mermaid.startOnLoad);
|
||||
mermaidAPI.initialize({ startOnLoad: mermaid.startOnLoad });
|
||||
}
|
||||
|
||||
if (typeof mermaid.ganttConfig !== 'undefined') {
|
||||
mermaidAPI.initialize({ gantt: mermaid.ganttConfig })
|
||||
mermaidAPI.initialize({ gantt: mermaid.ganttConfig });
|
||||
}
|
||||
|
||||
let txt
|
||||
let txt;
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const element = nodes[i]
|
||||
const element = nodes[i];
|
||||
|
||||
/*! Check if previously processed */
|
||||
if (!element.getAttribute('data-processed')) {
|
||||
element.setAttribute('data-processed', true)
|
||||
element.setAttribute('data-processed', true);
|
||||
} else {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = `mermaid-${Date.now()}`
|
||||
const id = `mermaid-${Date.now()}`;
|
||||
|
||||
// Fetch the graph definition including tags
|
||||
txt = element.innerHTML
|
||||
txt = element.innerHTML;
|
||||
|
||||
// transforms the html to pure text
|
||||
txt = he.decode(txt).trim().replace(/<br>/ig, '<br/>')
|
||||
txt = he
|
||||
.decode(txt)
|
||||
.trim()
|
||||
.replace(/<br>/gi, '<br/>');
|
||||
|
||||
mermaidAPI.render(id, txt, (svgCode, bindFunctions) => {
|
||||
element.innerHTML = svgCode
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(id)
|
||||
}
|
||||
if (bindFunctions) bindFunctions(element)
|
||||
}, element)
|
||||
mermaidAPI.render(
|
||||
id,
|
||||
txt,
|
||||
(svgCode, bindFunctions) => {
|
||||
element.innerHTML = svgCode;
|
||||
if (typeof callback !== 'undefined') {
|
||||
callback(id);
|
||||
}
|
||||
if (bindFunctions) bindFunctions(element);
|
||||
},
|
||||
element
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const initialize = function (config) {
|
||||
logger.debug('Initializing mermaid ')
|
||||
const initialize = function(config) {
|
||||
logger.debug('Initializing mermaid ');
|
||||
if (typeof config.mermaid !== 'undefined') {
|
||||
if (typeof config.mermaid.startOnLoad !== 'undefined') {
|
||||
mermaid.startOnLoad = config.mermaid.startOnLoad
|
||||
mermaid.startOnLoad = config.mermaid.startOnLoad;
|
||||
}
|
||||
if (typeof config.mermaid.htmlLabels !== 'undefined') {
|
||||
mermaid.htmlLabels = config.mermaid.htmlLabels
|
||||
mermaid.htmlLabels = config.mermaid.htmlLabels;
|
||||
}
|
||||
}
|
||||
mermaidAPI.initialize(config)
|
||||
}
|
||||
mermaidAPI.initialize(config);
|
||||
};
|
||||
|
||||
/**
|
||||
* ##contentLoaded
|
||||
* Callback function that is called when page is loaded. This functions fetches configuration for mermaid rendering and
|
||||
* calls init for rendering the mermaid diagrams on the page.
|
||||
*/
|
||||
const contentLoaded = function () {
|
||||
let config
|
||||
const contentLoaded = function() {
|
||||
let config;
|
||||
|
||||
if (mermaid.startOnLoad) {
|
||||
// No config found, do check API config
|
||||
config = mermaidAPI.getConfig()
|
||||
config = mermaidAPI.getConfig();
|
||||
if (config.startOnLoad) {
|
||||
mermaid.init()
|
||||
mermaid.init();
|
||||
}
|
||||
} else {
|
||||
if (typeof mermaid.startOnLoad === 'undefined') {
|
||||
logger.debug('In start, no config')
|
||||
config = mermaidAPI.getConfig()
|
||||
logger.debug('In start, no config');
|
||||
config = mermaidAPI.getConfig();
|
||||
if (config.startOnLoad) {
|
||||
mermaid.init()
|
||||
mermaid.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
/*!
|
||||
* Wait for document loaded before starting the execution
|
||||
*/
|
||||
window.addEventListener('load', function () {
|
||||
contentLoaded()
|
||||
}, false)
|
||||
window.addEventListener(
|
||||
'load',
|
||||
function() {
|
||||
contentLoaded();
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
const mermaid = {
|
||||
@ -162,6 +178,6 @@ const mermaid = {
|
||||
initialize,
|
||||
|
||||
contentLoaded
|
||||
}
|
||||
};
|
||||
|
||||
export default mermaid
|
||||
export default mermaid;
|
||||
|
@ -1,195 +1,200 @@
|
||||
/* eslint-env jasmine */
|
||||
import mermaid from './mermaid'
|
||||
import flowDb from './diagrams/flowchart/flowDb'
|
||||
import flowParser from './diagrams/flowchart/parser/flow'
|
||||
import flowRenderer from './diagrams/flowchart/flowRenderer'
|
||||
import mermaid from './mermaid';
|
||||
import flowDb from './diagrams/flowchart/flowDb';
|
||||
import flowParser from './diagrams/flowchart/parser/flow';
|
||||
import flowRenderer from './diagrams/flowchart/flowRenderer';
|
||||
|
||||
describe('when using mermaid and ', function () {
|
||||
describe('when detecting chart type ', function () {
|
||||
it('should not start rendering with mermaid.startOnLoad set to false', function () {
|
||||
mermaid.startOnLoad = false
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>'
|
||||
spyOn(mermaid, 'init')
|
||||
mermaid.contentLoaded()
|
||||
expect(mermaid.init).not.toHaveBeenCalled()
|
||||
})
|
||||
describe('when using mermaid and ', function() {
|
||||
describe('when detecting chart type ', function() {
|
||||
it('should not start rendering with mermaid.startOnLoad set to false', function() {
|
||||
mermaid.startOnLoad = false;
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
|
||||
spyOn(mermaid, 'init');
|
||||
mermaid.contentLoaded();
|
||||
expect(mermaid.init).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should start rendering with both startOnLoad set', function () {
|
||||
mermaid.startOnLoad = true
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>'
|
||||
spyOn(mermaid, 'init')
|
||||
mermaid.contentLoaded()
|
||||
expect(mermaid.init).toHaveBeenCalled()
|
||||
})
|
||||
it('should start rendering with both startOnLoad set', function() {
|
||||
mermaid.startOnLoad = true;
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
|
||||
spyOn(mermaid, 'init');
|
||||
mermaid.contentLoaded();
|
||||
expect(mermaid.init).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should start rendering with mermaid.startOnLoad', function () {
|
||||
mermaid.startOnLoad = true
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>'
|
||||
spyOn(mermaid, 'init')
|
||||
mermaid.contentLoaded()
|
||||
expect(mermaid.init).toHaveBeenCalled()
|
||||
})
|
||||
it('should start rendering with mermaid.startOnLoad', function() {
|
||||
mermaid.startOnLoad = true;
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
|
||||
spyOn(mermaid, 'init');
|
||||
mermaid.contentLoaded();
|
||||
expect(mermaid.init).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should start rendering as a default with no changes performed', function () {
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>'
|
||||
spyOn(mermaid, 'init')
|
||||
mermaid.contentLoaded()
|
||||
expect(mermaid.init).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
it('should start rendering as a default with no changes performed', function() {
|
||||
document.body.innerHTML = '<div class="mermaid">graph TD;\na;</div>';
|
||||
spyOn(mermaid, 'init');
|
||||
mermaid.contentLoaded();
|
||||
expect(mermaid.init).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when calling addEdges ', function () {
|
||||
beforeEach(function () {
|
||||
flowParser.parser.yy = flowDb
|
||||
flowDb.clear()
|
||||
})
|
||||
it('it should handle edges with text', function () {
|
||||
flowParser.parser.parse('graph TD;A-->|text ex|B;')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
describe('when calling addEdges ', function() {
|
||||
beforeEach(function() {
|
||||
flowParser.parser.yy = flowDb;
|
||||
flowDb.clear();
|
||||
});
|
||||
it('it should handle edges with text', function() {
|
||||
flowParser.parser.parse('graph TD;A-->|text ex|B;');
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('normal')
|
||||
expect(options.label.match('text ex')).toBeTruthy()
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('normal');
|
||||
expect(options.label.match('text ex')).toBeTruthy();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
|
||||
it('should handle edges without text', function () {
|
||||
flowParser.parser.parse('graph TD;A-->B;')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
it('should handle edges without text', function() {
|
||||
flowParser.parser.parse('graph TD;A-->B;');
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('normal')
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('normal');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
|
||||
it('should handle open-ended edges', function () {
|
||||
flowParser.parser.parse('graph TD;A---B;')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
it('should handle open-ended edges', function() {
|
||||
flowParser.parser.parse('graph TD;A---B;');
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('none')
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('none');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
|
||||
it('should handle edges with styles defined', function () {
|
||||
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
it('should handle edges with styles defined', function() {
|
||||
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('none')
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;')
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('none');
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
it('should handle edges with interpolation defined', function () {
|
||||
flowParser.parser.parse('graph TD;A---B; linkStyle 0 interpolate basis')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
it('should handle edges with interpolation defined', function() {
|
||||
flowParser.parser.parse('graph TD;A---B; linkStyle 0 interpolate basis');
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('none')
|
||||
expect(options.curve).toBe('basis') // mocked as string
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('none');
|
||||
expect(options.curve).toBe('basis'); // mocked as string
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
it('should handle edges with text and styles defined', function () {
|
||||
flowParser.parser.parse('graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
it('should handle edges with text and styles defined', function() {
|
||||
flowParser.parser.parse(
|
||||
'graph TD;A---|the text|B; linkStyle 0 stroke:val1,stroke-width:val2;'
|
||||
);
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('none')
|
||||
expect(options.label.match('the text')).toBeTruthy()
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;')
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('none');
|
||||
expect(options.label.match('the text')).toBeTruthy();
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
|
||||
it('should set fill to "none" by default when handling edges', function () {
|
||||
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
it('should set fill to "none" by default when handling edges', function() {
|
||||
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2;');
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('none')
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;')
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('none');
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:none;');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
|
||||
it('should not set fill to none if fill is set in linkStyle', function () {
|
||||
flowParser.parser.parse('graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;')
|
||||
flowParser.parser.yy.getVertices()
|
||||
const edges = flowParser.parser.yy.getEdges()
|
||||
it('should not set fill to none if fill is set in linkStyle', function() {
|
||||
flowParser.parser.parse(
|
||||
'graph TD;A---B; linkStyle 0 stroke:val1,stroke-width:val2,fill:blue;'
|
||||
);
|
||||
flowParser.parser.yy.getVertices();
|
||||
const edges = flowParser.parser.yy.getEdges();
|
||||
const mockG = {
|
||||
setEdge: function (start, end, options) {
|
||||
expect(start).toBe('A')
|
||||
expect(end).toBe('B')
|
||||
expect(options.arrowhead).toBe('none')
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;')
|
||||
setEdge: function(start, end, options) {
|
||||
expect(start).toBe('A');
|
||||
expect(end).toBe('B');
|
||||
expect(options.arrowhead).toBe('none');
|
||||
expect(options.style).toBe('stroke:val1;stroke-width:val2;fill:blue;');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
flowRenderer.addEdges(edges, mockG)
|
||||
})
|
||||
})
|
||||
flowRenderer.addEdges(edges, mockG);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checking validity of input ', function () {
|
||||
it('it should throw for an invalid definiton', function () {
|
||||
expect(() => mermaid.parse('this is not a mermaid diagram definition')).toThrow()
|
||||
})
|
||||
describe('checking validity of input ', function() {
|
||||
it('it should throw for an invalid definiton', function() {
|
||||
expect(() => mermaid.parse('this is not a mermaid diagram definition')).toThrow();
|
||||
});
|
||||
|
||||
it('it should not throw for a valid flow definition', function () {
|
||||
expect(() => mermaid.parse('graph TD;A--x|text including URL space|B;')).not.toThrow()
|
||||
})
|
||||
it('it should throw for an invalid flow definition', function () {
|
||||
expect(() => mermaid.parse('graph TQ;A--x|text including URL space|B;')).toThrow()
|
||||
})
|
||||
it('it should not throw for a valid flow definition', function() {
|
||||
expect(() => mermaid.parse('graph TD;A--x|text including URL space|B;')).not.toThrow();
|
||||
});
|
||||
it('it should throw for an invalid flow definition', function() {
|
||||
expect(() => mermaid.parse('graph TQ;A--x|text including URL space|B;')).toThrow();
|
||||
});
|
||||
|
||||
it('it should not throw for a valid sequenceDiagram definition', function () {
|
||||
const text = 'sequenceDiagram\n' +
|
||||
it('it should not throw for a valid sequenceDiagram definition', function() {
|
||||
const text =
|
||||
'sequenceDiagram\n' +
|
||||
'Alice->Bob: Hello Bob, how are you?\n\n' +
|
||||
'%% Comment\n' +
|
||||
'Note right of Bob: Bob thinks\n' +
|
||||
@ -197,12 +202,13 @@ describe('when using mermaid and ', function () {
|
||||
'Bob-->Alice: I am good thanks!\n' +
|
||||
'else isSick\n' +
|
||||
'Bob-->Alice: Feel sick...\n' +
|
||||
'end'
|
||||
expect(() => mermaid.parse(text)).not.toThrow()
|
||||
})
|
||||
'end';
|
||||
expect(() => mermaid.parse(text)).not.toThrow();
|
||||
});
|
||||
|
||||
it('it should throw for an invalid sequenceDiagram definition', function () {
|
||||
const text = 'sequenceDiagram\n' +
|
||||
it('it should throw for an invalid sequenceDiagram definition', function() {
|
||||
const text =
|
||||
'sequenceDiagram\n' +
|
||||
'Alice:->Bob: Hello Bob, how are you?\n\n' +
|
||||
'%% Comment\n' +
|
||||
'Note right of Bob: Bob thinks\n' +
|
||||
@ -210,8 +216,8 @@ describe('when using mermaid and ', function () {
|
||||
'Bob-->Alice: I am good thanks!\n' +
|
||||
'else isSick\n' +
|
||||
'Bob-->Alice: Feel sick...\n' +
|
||||
'end'
|
||||
expect(() => mermaid.parse(text)).toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
'end';
|
||||
expect(() => mermaid.parse(text)).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,38 +9,38 @@
|
||||
* In addition to the render function, a number of behavioral configuration options are available.
|
||||
*
|
||||
* @name mermaidAPI
|
||||
*/
|
||||
import * as d3 from 'd3'
|
||||
import scope from 'scope-css'
|
||||
import pkg from '../package.json'
|
||||
import { setConfig, getConfig } from './config'
|
||||
import { logger, setLogLevel } from './logger'
|
||||
import utils from './utils'
|
||||
import flowRenderer from './diagrams/flowchart/flowRenderer'
|
||||
import flowParser from './diagrams/flowchart/parser/flow'
|
||||
import flowDb from './diagrams/flowchart/flowDb'
|
||||
import sequenceRenderer from './diagrams/sequence/sequenceRenderer'
|
||||
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram'
|
||||
import sequenceDb from './diagrams/sequence/sequenceDb'
|
||||
import ganttRenderer from './diagrams/gantt/ganttRenderer'
|
||||
import ganttParser from './diagrams/gantt/parser/gantt'
|
||||
import ganttDb from './diagrams/gantt/ganttDb'
|
||||
import classRenderer from './diagrams/class/classRenderer'
|
||||
import classParser from './diagrams/class/parser/classDiagram'
|
||||
import classDb from './diagrams/class/classDb'
|
||||
import gitGraphRenderer from './diagrams/git/gitGraphRenderer'
|
||||
import gitGraphParser from './diagrams/git/parser/gitGraph'
|
||||
import gitGraphAst from './diagrams/git/gitGraphAst'
|
||||
import infoRenderer from './diagrams/info/infoRenderer'
|
||||
import infoParser from './diagrams/info/parser/info'
|
||||
import infoDb from './diagrams/info/infoDb'
|
||||
import pieRenderer from './diagrams/pie/pieRenderer'
|
||||
import pieParser from './diagrams/pie/parser/pie'
|
||||
import pieDb from './diagrams/pie/pieDb'
|
||||
*/
|
||||
import * as d3 from 'd3';
|
||||
import scope from 'scope-css';
|
||||
import pkg from '../package.json';
|
||||
import { setConfig, getConfig } from './config';
|
||||
import { logger, setLogLevel } from './logger';
|
||||
import utils from './utils';
|
||||
import flowRenderer from './diagrams/flowchart/flowRenderer';
|
||||
import flowParser from './diagrams/flowchart/parser/flow';
|
||||
import flowDb from './diagrams/flowchart/flowDb';
|
||||
import sequenceRenderer from './diagrams/sequence/sequenceRenderer';
|
||||
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram';
|
||||
import sequenceDb from './diagrams/sequence/sequenceDb';
|
||||
import ganttRenderer from './diagrams/gantt/ganttRenderer';
|
||||
import ganttParser from './diagrams/gantt/parser/gantt';
|
||||
import ganttDb from './diagrams/gantt/ganttDb';
|
||||
import classRenderer from './diagrams/class/classRenderer';
|
||||
import classParser from './diagrams/class/parser/classDiagram';
|
||||
import classDb from './diagrams/class/classDb';
|
||||
import gitGraphRenderer from './diagrams/git/gitGraphRenderer';
|
||||
import gitGraphParser from './diagrams/git/parser/gitGraph';
|
||||
import gitGraphAst from './diagrams/git/gitGraphAst';
|
||||
import infoRenderer from './diagrams/info/infoRenderer';
|
||||
import infoParser from './diagrams/info/parser/info';
|
||||
import infoDb from './diagrams/info/infoDb';
|
||||
import pieRenderer from './diagrams/pie/pieRenderer';
|
||||
import pieParser from './diagrams/pie/parser/pie';
|
||||
import pieDb from './diagrams/pie/pieDb';
|
||||
|
||||
const themes = {}
|
||||
const themes = {};
|
||||
for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
|
||||
themes[themeName] = require(`./themes/${themeName}/index.scss`)
|
||||
themes[themeName] = require(`./themes/${themeName}/index.scss`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,22 +75,21 @@ for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
|
||||
* @name Configuration
|
||||
*/
|
||||
const config = {
|
||||
|
||||
/** theme , the CSS style sheet
|
||||
*
|
||||
* **theme** - Choose one of the built-in themes:
|
||||
* * default
|
||||
* * forest
|
||||
* * dark
|
||||
* * neutral.
|
||||
* To disable any pre-defined mermaid theme, use "null".
|
||||
*
|
||||
* **themeCSS** - Use your own CSS. This overrides **theme**.
|
||||
* <pre>
|
||||
* "theme": "forest",
|
||||
* "themeCSS": ".node rect { fill: red; }"
|
||||
* </pre>
|
||||
*/
|
||||
*
|
||||
* **theme** - Choose one of the built-in themes:
|
||||
* * default
|
||||
* * forest
|
||||
* * dark
|
||||
* * neutral.
|
||||
* To disable any pre-defined mermaid theme, use "null".
|
||||
*
|
||||
* **themeCSS** - Use your own CSS. This overrides **theme**.
|
||||
* <pre>
|
||||
* "theme": "forest",
|
||||
* "themeCSS": ".node rect { fill: red; }"
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
theme: 'default',
|
||||
themeCSS: undefined,
|
||||
@ -149,7 +148,6 @@ const config = {
|
||||
* The object containing configurations specific for sequence diagrams
|
||||
*/
|
||||
sequence: {
|
||||
|
||||
/**
|
||||
* margin to the right and left of the sequence diagram.
|
||||
* **Default value 50**.
|
||||
@ -234,7 +232,6 @@ const config = {
|
||||
* **Default value false**.
|
||||
*/
|
||||
showSequenceNumbers: false
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@ -303,100 +300,100 @@ const config = {
|
||||
},
|
||||
class: {},
|
||||
git: {}
|
||||
}
|
||||
};
|
||||
|
||||
setLogLevel(config.logLevel)
|
||||
setConfig(config)
|
||||
setLogLevel(config.logLevel);
|
||||
setConfig(config);
|
||||
|
||||
function parse (text) {
|
||||
const graphType = utils.detectType(text)
|
||||
let parser
|
||||
function parse(text) {
|
||||
const graphType = utils.detectType(text);
|
||||
let parser;
|
||||
|
||||
logger.debug('Type ' + graphType)
|
||||
logger.debug('Type ' + graphType);
|
||||
switch (graphType) {
|
||||
case 'git':
|
||||
parser = gitGraphParser
|
||||
parser.parser.yy = gitGraphAst
|
||||
break
|
||||
parser = gitGraphParser;
|
||||
parser.parser.yy = gitGraphAst;
|
||||
break;
|
||||
case 'flowchart':
|
||||
parser = flowParser
|
||||
parser.parser.yy = flowDb
|
||||
break
|
||||
parser = flowParser;
|
||||
parser.parser.yy = flowDb;
|
||||
break;
|
||||
case 'sequence':
|
||||
parser = sequenceParser
|
||||
parser.parser.yy = sequenceDb
|
||||
break
|
||||
parser = sequenceParser;
|
||||
parser.parser.yy = sequenceDb;
|
||||
break;
|
||||
case 'gantt':
|
||||
parser = ganttParser
|
||||
parser.parser.yy = ganttDb
|
||||
break
|
||||
parser = ganttParser;
|
||||
parser.parser.yy = ganttDb;
|
||||
break;
|
||||
case 'class':
|
||||
parser = classParser
|
||||
parser.parser.yy = classDb
|
||||
break
|
||||
parser = classParser;
|
||||
parser.parser.yy = classDb;
|
||||
break;
|
||||
case 'info':
|
||||
logger.debug('info info info')
|
||||
console.warn('In API', pkg.version)
|
||||
logger.debug('info info info');
|
||||
console.warn('In API', pkg.version);
|
||||
|
||||
parser = infoParser
|
||||
parser.parser.yy = infoDb
|
||||
break
|
||||
parser = infoParser;
|
||||
parser.parser.yy = infoDb;
|
||||
break;
|
||||
case 'pie':
|
||||
logger.debug('pie')
|
||||
parser = pieParser
|
||||
parser.parser.yy = pieDb
|
||||
break
|
||||
logger.debug('pie');
|
||||
parser = pieParser;
|
||||
parser.parser.yy = pieDb;
|
||||
break;
|
||||
}
|
||||
|
||||
parser.parser.yy.parseError = (str, hash) => {
|
||||
const error = { str, hash }
|
||||
throw error
|
||||
}
|
||||
const error = { str, hash };
|
||||
throw error;
|
||||
};
|
||||
|
||||
parser.parse(text)
|
||||
parser.parse(text);
|
||||
}
|
||||
|
||||
export const encodeEntities = function (text) {
|
||||
let txt = text
|
||||
export const encodeEntities = function(text) {
|
||||
let txt = text;
|
||||
|
||||
txt = txt.replace(/style.*:\S*#.*;/g, function (s) {
|
||||
const innerTxt = s.substring(0, s.length - 1)
|
||||
return innerTxt
|
||||
})
|
||||
txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) {
|
||||
const innerTxt = s.substring(0, s.length - 1)
|
||||
return innerTxt
|
||||
})
|
||||
txt = txt.replace(/style.*:\S*#.*;/g, function(s) {
|
||||
const innerTxt = s.substring(0, s.length - 1);
|
||||
return innerTxt;
|
||||
});
|
||||
txt = txt.replace(/classDef.*:\S*#.*;/g, function(s) {
|
||||
const innerTxt = s.substring(0, s.length - 1);
|
||||
return innerTxt;
|
||||
});
|
||||
|
||||
txt = txt.replace(/#\w+;/g, function (s) {
|
||||
const innerTxt = s.substring(1, s.length - 1)
|
||||
txt = txt.replace(/#\w+;/g, function(s) {
|
||||
const innerTxt = s.substring(1, s.length - 1);
|
||||
|
||||
const isInt = /^\+?\d+$/.test(innerTxt)
|
||||
const isInt = /^\+?\d+$/.test(innerTxt);
|
||||
if (isInt) {
|
||||
return 'fl°°' + innerTxt + '¶ß'
|
||||
return 'fl°°' + innerTxt + '¶ß';
|
||||
} else {
|
||||
return 'fl°' + innerTxt + '¶ß'
|
||||
return 'fl°' + innerTxt + '¶ß';
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return txt
|
||||
}
|
||||
return txt;
|
||||
};
|
||||
|
||||
export const decodeEntities = function (text) {
|
||||
let txt = text
|
||||
export const decodeEntities = function(text) {
|
||||
let txt = text;
|
||||
|
||||
txt = txt.replace(/fl°°/g, function () {
|
||||
return '&#'
|
||||
})
|
||||
txt = txt.replace(/fl°/g, function () {
|
||||
return '&'
|
||||
})
|
||||
txt = txt.replace(/¶ß/g, function () {
|
||||
return ';'
|
||||
})
|
||||
txt = txt.replace(/fl°°/g, function() {
|
||||
return '&#';
|
||||
});
|
||||
txt = txt.replace(/fl°/g, function() {
|
||||
return '&';
|
||||
});
|
||||
txt = txt.replace(/¶ß/g, function() {
|
||||
return ';';
|
||||
});
|
||||
|
||||
return txt
|
||||
}
|
||||
return txt;
|
||||
};
|
||||
/**
|
||||
* Function that renders an svg with a graph from a chart definition. Usage example below.
|
||||
*
|
||||
@ -419,183 +416,209 @@ export const decodeEntities = function (text) {
|
||||
* provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
|
||||
* completed.
|
||||
*/
|
||||
const render = function (id, txt, cb, container) {
|
||||
const render = function(id, txt, cb, container) {
|
||||
if (typeof container !== 'undefined') {
|
||||
container.innerHTML = ''
|
||||
container.innerHTML = '';
|
||||
|
||||
d3.select(container).append('div')
|
||||
d3.select(container)
|
||||
.append('div')
|
||||
.attr('id', 'd' + id)
|
||||
.append('svg')
|
||||
.attr('id', id)
|
||||
.attr('width', '100%')
|
||||
.attr('xmlns', 'http://www.w3.org/2000/svg')
|
||||
.append('g')
|
||||
.append('g');
|
||||
} else {
|
||||
const element = document.querySelector('#' + 'd' + id)
|
||||
const element = document.querySelector('#' + 'd' + id);
|
||||
if (element) {
|
||||
element.innerHTML = ''
|
||||
element.innerHTML = '';
|
||||
}
|
||||
|
||||
d3.select('body').append('div')
|
||||
d3.select('body')
|
||||
.append('div')
|
||||
.attr('id', 'd' + id)
|
||||
.append('svg')
|
||||
.attr('id', id)
|
||||
.attr('width', '100%')
|
||||
.attr('xmlns', 'http://www.w3.org/2000/svg')
|
||||
.append('g')
|
||||
.append('g');
|
||||
}
|
||||
|
||||
window.txt = txt
|
||||
txt = encodeEntities(txt)
|
||||
window.txt = txt;
|
||||
txt = encodeEntities(txt);
|
||||
|
||||
const element = d3.select('#d' + id).node()
|
||||
const graphType = utils.detectType(txt)
|
||||
const element = d3.select('#d' + id).node();
|
||||
const graphType = utils.detectType(txt);
|
||||
|
||||
// insert inline style into svg
|
||||
const svg = element.firstChild
|
||||
const firstChild = svg.firstChild
|
||||
const svg = element.firstChild;
|
||||
const firstChild = svg.firstChild;
|
||||
|
||||
// pre-defined theme
|
||||
let style = themes[config.theme]
|
||||
let style = themes[config.theme];
|
||||
if (style === undefined) {
|
||||
style = ''
|
||||
style = '';
|
||||
}
|
||||
|
||||
// user provided theme CSS
|
||||
if (config.themeCSS !== undefined) {
|
||||
style += `\n${config.themeCSS}`
|
||||
style += `\n${config.themeCSS}`;
|
||||
}
|
||||
|
||||
// classDef
|
||||
if (graphType === 'flowchart') {
|
||||
const classes = flowRenderer.getClasses(txt)
|
||||
const classes = flowRenderer.getClasses(txt);
|
||||
for (const className in classes) {
|
||||
style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }`
|
||||
style += `\n.${className} > * { ${classes[className].styles.join(
|
||||
' !important; '
|
||||
)} !important; }`;
|
||||
}
|
||||
}
|
||||
|
||||
const style1 = document.createElement('style')
|
||||
style1.innerHTML = scope(style, `#${id}`)
|
||||
svg.insertBefore(style1, firstChild)
|
||||
const style1 = document.createElement('style');
|
||||
style1.innerHTML = scope(style, `#${id}`);
|
||||
svg.insertBefore(style1, firstChild);
|
||||
|
||||
const style2 = document.createElement('style')
|
||||
const cs = window.getComputedStyle(svg)
|
||||
const style2 = document.createElement('style');
|
||||
const cs = window.getComputedStyle(svg);
|
||||
style2.innerHTML = `#${id} {
|
||||
color: ${cs.color};
|
||||
font: ${cs.font};
|
||||
}`
|
||||
svg.insertBefore(style2, firstChild)
|
||||
}`;
|
||||
svg.insertBefore(style2, firstChild);
|
||||
|
||||
switch (graphType) {
|
||||
case 'git':
|
||||
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
||||
gitGraphRenderer.setConf(config.git)
|
||||
gitGraphRenderer.draw(txt, id, false)
|
||||
break
|
||||
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
gitGraphRenderer.setConf(config.git);
|
||||
gitGraphRenderer.draw(txt, id, false);
|
||||
break;
|
||||
case 'flowchart':
|
||||
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
||||
flowRenderer.setConf(config.flowchart)
|
||||
flowRenderer.draw(txt, id, false)
|
||||
break
|
||||
config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
flowRenderer.setConf(config.flowchart);
|
||||
flowRenderer.draw(txt, id, false);
|
||||
break;
|
||||
case 'sequence':
|
||||
config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
||||
if (config.sequenceDiagram) { // backwards compatibility
|
||||
sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram))
|
||||
console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.')
|
||||
config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
if (config.sequenceDiagram) {
|
||||
// backwards compatibility
|
||||
sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram));
|
||||
console.error(
|
||||
'`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.'
|
||||
);
|
||||
} else {
|
||||
sequenceRenderer.setConf(config.sequence)
|
||||
sequenceRenderer.setConf(config.sequence);
|
||||
}
|
||||
sequenceRenderer.draw(txt, id)
|
||||
break
|
||||
sequenceRenderer.draw(txt, id);
|
||||
break;
|
||||
case 'gantt':
|
||||
config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
||||
ganttRenderer.setConf(config.gantt)
|
||||
ganttRenderer.draw(txt, id)
|
||||
break
|
||||
config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
ganttRenderer.setConf(config.gantt);
|
||||
ganttRenderer.draw(txt, id);
|
||||
break;
|
||||
case 'class':
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
||||
classRenderer.setConf(config.class)
|
||||
classRenderer.draw(txt, id)
|
||||
break
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
classRenderer.setConf(config.class);
|
||||
classRenderer.draw(txt, id);
|
||||
break;
|
||||
case 'info':
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
||||
infoRenderer.setConf(config.class)
|
||||
infoRenderer.draw(txt, id, pkg.version)
|
||||
break
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
infoRenderer.setConf(config.class);
|
||||
infoRenderer.draw(txt, id, pkg.version);
|
||||
break;
|
||||
case 'pie':
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
|
||||
pieRenderer.setConf(config.class)
|
||||
pieRenderer.draw(txt, id, pkg.version)
|
||||
break
|
||||
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
|
||||
pieRenderer.setConf(config.class);
|
||||
pieRenderer.draw(txt, id, pkg.version);
|
||||
break;
|
||||
}
|
||||
|
||||
d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml')
|
||||
d3.select(`[id="${id}"]`)
|
||||
.selectAll('foreignobject > *')
|
||||
.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
|
||||
let url = ''
|
||||
let url = '';
|
||||
if (config.arrowMarkerAbsolute) {
|
||||
url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
|
||||
url = url.replace(/\(/g, '\\(')
|
||||
url = url.replace(/\)/g, '\\)')
|
||||
url =
|
||||
window.location.protocol +
|
||||
'//' +
|
||||
window.location.host +
|
||||
window.location.pathname +
|
||||
window.location.search;
|
||||
url = url.replace(/\(/g, '\\(');
|
||||
url = url.replace(/\)/g, '\\)');
|
||||
}
|
||||
|
||||
// Fix for when the base tag is used
|
||||
let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g')
|
||||
let svgCode = d3
|
||||
.select('#d' + id)
|
||||
.node()
|
||||
.innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g');
|
||||
|
||||
svgCode = decodeEntities(svgCode)
|
||||
svgCode = decodeEntities(svgCode);
|
||||
|
||||
if (typeof cb !== 'undefined') {
|
||||
switch (graphType) {
|
||||
case 'flowchart':
|
||||
cb(svgCode, flowDb.bindFunctions)
|
||||
break
|
||||
cb(svgCode, flowDb.bindFunctions);
|
||||
break;
|
||||
case 'gantt':
|
||||
cb(svgCode, ganttDb.bindFunctions)
|
||||
break
|
||||
cb(svgCode, ganttDb.bindFunctions);
|
||||
break;
|
||||
default:
|
||||
cb(svgCode)
|
||||
cb(svgCode);
|
||||
}
|
||||
} else {
|
||||
logger.debug('CB = undefined!')
|
||||
logger.debug('CB = undefined!');
|
||||
}
|
||||
|
||||
const node = d3.select('#d' + id).node()
|
||||
const node = d3.select('#d' + id).node();
|
||||
if (node !== null && typeof node.remove === 'function') {
|
||||
d3.select('#d' + id).node().remove()
|
||||
d3.select('#d' + id)
|
||||
.node()
|
||||
.remove();
|
||||
}
|
||||
|
||||
return svgCode
|
||||
}
|
||||
return svgCode;
|
||||
};
|
||||
|
||||
const setConf = function (cnf) {
|
||||
const setConf = function(cnf) {
|
||||
// Top level initially mermaid, gflow, sequenceDiagram and gantt
|
||||
const lvl1Keys = Object.keys(cnf)
|
||||
const lvl1Keys = Object.keys(cnf);
|
||||
for (let i = 0; i < lvl1Keys.length; i++) {
|
||||
if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
|
||||
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]])
|
||||
const lvl2Keys = Object.keys(cnf[lvl1Keys[i]]);
|
||||
|
||||
for (let j = 0; j < lvl2Keys.length; j++) {
|
||||
logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
|
||||
logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j]);
|
||||
if (typeof config[lvl1Keys[i]] === 'undefined') {
|
||||
config[lvl1Keys[i]] = {}
|
||||
config[lvl1Keys[i]] = {};
|
||||
}
|
||||
logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
|
||||
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]]
|
||||
logger.debug(
|
||||
'Setting config: ' +
|
||||
lvl1Keys[i] +
|
||||
' ' +
|
||||
lvl2Keys[j] +
|
||||
' to ' +
|
||||
cnf[lvl1Keys[i]][lvl2Keys[j]]
|
||||
);
|
||||
config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]];
|
||||
}
|
||||
} else {
|
||||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]]
|
||||
config[lvl1Keys[i]] = cnf[lvl1Keys[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function initialize (options) {
|
||||
logger.debug('Initializing mermaidAPI ', pkg.version)
|
||||
function initialize(options) {
|
||||
logger.debug('Initializing mermaidAPI ', pkg.version);
|
||||
|
||||
// Update default config with options supplied at initialization
|
||||
if (typeof options === 'object') {
|
||||
setConf(options)
|
||||
setConf(options);
|
||||
}
|
||||
setConfig(config)
|
||||
setLogLevel(config.logLevel)
|
||||
setConfig(config);
|
||||
setLogLevel(config.logLevel);
|
||||
}
|
||||
|
||||
// function getConfig () {
|
||||
@ -608,9 +631,9 @@ const mermaidAPI = {
|
||||
parse,
|
||||
initialize,
|
||||
getConfig
|
||||
}
|
||||
};
|
||||
|
||||
export default mermaidAPI
|
||||
export default mermaidAPI;
|
||||
/**
|
||||
* ## mermaidAPI configuration defaults
|
||||
* <pre>
|
||||
|
@ -1,45 +1,45 @@
|
||||
/* eslint-env jasmine */
|
||||
import mermaidAPI from './mermaidAPI'
|
||||
import mermaidAPI from './mermaidAPI';
|
||||
|
||||
describe('when using mermaidAPI and ', function () {
|
||||
describe('doing initialize ', function () {
|
||||
beforeEach(function () {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
describe('when using mermaidAPI and ', function() {
|
||||
describe('doing initialize ', function() {
|
||||
beforeEach(function() {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
it('should copy a literal into the configuration', function () {
|
||||
const orgConfig = mermaidAPI.getConfig()
|
||||
expect(orgConfig.testLiteral).toBe(undefined)
|
||||
it('should copy a literal into the configuration', function() {
|
||||
const orgConfig = mermaidAPI.getConfig();
|
||||
expect(orgConfig.testLiteral).toBe(undefined);
|
||||
|
||||
mermaidAPI.initialize({ 'testLiteral': true })
|
||||
const config = mermaidAPI.getConfig()
|
||||
mermaidAPI.initialize({ testLiteral: true });
|
||||
const config = mermaidAPI.getConfig();
|
||||
|
||||
expect(config.testLiteral).toBe(true)
|
||||
})
|
||||
it('should copy a an object into the configuration', function () {
|
||||
const orgConfig = mermaidAPI.getConfig()
|
||||
expect(orgConfig.testObject).toBe(undefined)
|
||||
expect(config.testLiteral).toBe(true);
|
||||
});
|
||||
it('should copy a an object into the configuration', function() {
|
||||
const orgConfig = mermaidAPI.getConfig();
|
||||
expect(orgConfig.testObject).toBe(undefined);
|
||||
|
||||
const object = {
|
||||
test1: 1,
|
||||
test2: false
|
||||
}
|
||||
};
|
||||
|
||||
mermaidAPI.initialize({ 'testObject': object })
|
||||
mermaidAPI.initialize({ 'testObject': { 'test3': true } })
|
||||
const config = mermaidAPI.getConfig()
|
||||
mermaidAPI.initialize({ testObject: object });
|
||||
mermaidAPI.initialize({ testObject: { test3: true } });
|
||||
const config = mermaidAPI.getConfig();
|
||||
|
||||
expect(config.testObject.test1).toBe(1)
|
||||
expect(config.testObject.test2).toBe(false)
|
||||
expect(config.testObject.test3).toBe(true)
|
||||
})
|
||||
})
|
||||
describe('checking validity of input ', function () {
|
||||
it('it should throw for an invalid definiton', function () {
|
||||
expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow()
|
||||
})
|
||||
it('it should not throw for a valid definiton', function () {
|
||||
expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow()
|
||||
})
|
||||
})
|
||||
})
|
||||
expect(config.testObject.test1).toBe(1);
|
||||
expect(config.testObject.test2).toBe(false);
|
||||
expect(config.testObject.test3).toBe(true);
|
||||
});
|
||||
});
|
||||
describe('checking validity of input ', function() {
|
||||
it('it should throw for an invalid definiton', function() {
|
||||
expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow();
|
||||
});
|
||||
it('it should not throw for a valid definiton', function() {
|
||||
expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
44
src/utils.js
44
src/utils.js
@ -1,5 +1,5 @@
|
||||
import * as d3 from 'd3'
|
||||
import { logger } from './logger'
|
||||
import * as d3 from 'd3';
|
||||
import { logger } from './logger';
|
||||
|
||||
/**
|
||||
* @function detectType
|
||||
@ -18,34 +18,34 @@ import { logger } from './logger'
|
||||
* @param {string} text The text defining the graph
|
||||
* @returns {string} A graph definition key
|
||||
*/
|
||||
export const detectType = function (text) {
|
||||
text = text.replace(/^\s*%%.*\n/g, '\n')
|
||||
logger.debug('Detecting diagram type based on the text ' + text)
|
||||
export const detectType = function(text) {
|
||||
text = text.replace(/^\s*%%.*\n/g, '\n');
|
||||
logger.debug('Detecting diagram type based on the text ' + text);
|
||||
if (text.match(/^\s*sequenceDiagram/)) {
|
||||
return 'sequence'
|
||||
return 'sequence';
|
||||
}
|
||||
|
||||
if (text.match(/^\s*gantt/)) {
|
||||
return 'gantt'
|
||||
return 'gantt';
|
||||
}
|
||||
|
||||
if (text.match(/^\s*classDiagram/)) {
|
||||
return 'class'
|
||||
return 'class';
|
||||
}
|
||||
|
||||
if (text.match(/^\s*gitGraph/)) {
|
||||
return 'git'
|
||||
return 'git';
|
||||
}
|
||||
|
||||
if (text.match(/^\s*info/)) {
|
||||
return 'info'
|
||||
return 'info';
|
||||
}
|
||||
if (text.match(/^\s*pie/)) {
|
||||
return 'pie'
|
||||
return 'pie';
|
||||
}
|
||||
|
||||
return 'flowchart'
|
||||
}
|
||||
return 'flowchart';
|
||||
};
|
||||
|
||||
/**
|
||||
* @function isSubstringInArray
|
||||
@ -54,23 +54,23 @@ export const detectType = function (text) {
|
||||
* @param {array} arr The array to search
|
||||
* @returns {number} the array index containing the substring or -1 if not present
|
||||
**/
|
||||
export const isSubstringInArray = function (str, arr) {
|
||||
export const isSubstringInArray = function(str, arr) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].match(str)) return i
|
||||
if (arr[i].match(str)) return i;
|
||||
}
|
||||
return -1
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
export const interpolateToCurve = (interpolate, defaultCurve) => {
|
||||
if (!interpolate) {
|
||||
return defaultCurve
|
||||
return defaultCurve;
|
||||
}
|
||||
const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`
|
||||
return d3[curveName] || defaultCurve
|
||||
}
|
||||
const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`;
|
||||
return d3[curveName] || defaultCurve;
|
||||
};
|
||||
|
||||
export default {
|
||||
detectType,
|
||||
isSubstringInArray,
|
||||
interpolateToCurve
|
||||
}
|
||||
};
|
||||
|
@ -1,39 +1,39 @@
|
||||
/* eslint-env jasmine */
|
||||
import utils from './utils'
|
||||
import utils from './utils';
|
||||
|
||||
describe('when detecting chart type ', function () {
|
||||
it('should handle a graph defintion', function () {
|
||||
const str = 'graph TB\nbfs1:queue'
|
||||
const type = utils.detectType(str)
|
||||
expect(type).toBe('flowchart')
|
||||
})
|
||||
it('should handle a graph defintion with leading spaces', function () {
|
||||
const str = ' graph TB\nbfs1:queue'
|
||||
const type = utils.detectType(str)
|
||||
expect(type).toBe('flowchart')
|
||||
})
|
||||
describe('when detecting chart type ', function() {
|
||||
it('should handle a graph defintion', function() {
|
||||
const str = 'graph TB\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('flowchart');
|
||||
});
|
||||
it('should handle a graph defintion with leading spaces', function() {
|
||||
const str = ' graph TB\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('flowchart');
|
||||
});
|
||||
|
||||
it('should handle a graph defintion with leading spaces and newline', function () {
|
||||
const str = ' \n graph TB\nbfs1:queue'
|
||||
const type = utils.detectType(str)
|
||||
expect(type).toBe('flowchart')
|
||||
})
|
||||
it('should handle a graph defintion for gitGraph', function () {
|
||||
const str = ' \n gitGraph TB:\nbfs1:queue'
|
||||
const type = utils.detectType(str)
|
||||
expect(type).toBe('git')
|
||||
})
|
||||
})
|
||||
it('should handle a graph defintion with leading spaces and newline', function() {
|
||||
const str = ' \n graph TB\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('flowchart');
|
||||
});
|
||||
it('should handle a graph defintion for gitGraph', function() {
|
||||
const str = ' \n gitGraph TB:\nbfs1:queue';
|
||||
const type = utils.detectType(str);
|
||||
expect(type).toBe('git');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when finding substring in array ', function () {
|
||||
it('should return the array index that contains the substring', function () {
|
||||
const arr = ['stroke:val1', 'fill:val2']
|
||||
const result = utils.isSubstringInArray('fill', arr)
|
||||
expect(result).toEqual(1)
|
||||
})
|
||||
it('should return -1 if the substring is not found in the array', function () {
|
||||
const arr = ['stroke:val1', 'stroke-width:val2']
|
||||
const result = utils.isSubstringInArray('fill', arr)
|
||||
expect(result).toEqual(-1)
|
||||
})
|
||||
})
|
||||
describe('when finding substring in array ', function() {
|
||||
it('should return the array index that contains the substring', function() {
|
||||
const arr = ['stroke:val1', 'fill:val2'];
|
||||
const result = utils.isSubstringInArray('fill', arr);
|
||||
expect(result).toEqual(1);
|
||||
});
|
||||
it('should return -1 if the substring is not found in the array', function() {
|
||||
const arr = ['stroke:val1', 'stroke-width:val2'];
|
||||
const result = utils.isSubstringInArray('fill', arr);
|
||||
expect(result).toEqual(-1);
|
||||
});
|
||||
});
|
||||
|
273
yarn.lock
273
yarn.lock
@ -1528,6 +1528,11 @@ acorn-jsx@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
|
||||
|
||||
acorn-jsx@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f"
|
||||
integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==
|
||||
|
||||
acorn-walk@^6.0.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
|
||||
@ -1540,6 +1545,11 @@ acorn@^6.0.1, acorn@^6.0.2:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.4.tgz#77377e7353b72ec5104550aa2d2097a2fd40b754"
|
||||
|
||||
acorn@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a"
|
||||
integrity sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==
|
||||
|
||||
agent-base@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||
@ -1575,6 +1585,16 @@ ajv@^6.1.0:
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.3.0"
|
||||
|
||||
ajv@^6.10.0, ajv@^6.10.2:
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
|
||||
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
amdefine@>=0.0.4:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
|
||||
@ -1591,9 +1611,10 @@ ansi-escapes@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.0.0.tgz#ec3e8b4e9f8064fc02c3ac9b65f1c275bda8ef92"
|
||||
|
||||
ansi-escapes@^3.1.0:
|
||||
ansi-escapes@^3.1.0, ansi-escapes@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
|
||||
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
|
||||
|
||||
ansi-html@0.0.7, ansi-html@^0.0.7:
|
||||
version "0.0.7"
|
||||
@ -2383,6 +2404,11 @@ callsites@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
camel-case@3.0.x:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
|
||||
@ -2504,6 +2530,11 @@ chardet@^0.4.0:
|
||||
version "0.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
|
||||
|
||||
chardet@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
|
||||
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
|
||||
|
||||
check-more-types@2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
|
||||
@ -3394,9 +3425,10 @@ debug@3.2.6, debug@^3.2.5, debug@^3.2.6:
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@4.1.1, debug@^4.1.1:
|
||||
debug@4.1.1, debug@^4.0.1, debug@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
|
||||
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
@ -3627,6 +3659,13 @@ doctrine@^2.1.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
documentation@^12.0.1:
|
||||
version "12.0.1"
|
||||
resolved "https://registry.yarnpkg.com/documentation/-/documentation-12.0.1.tgz#4abe263d5415f3ed7ee737829921ef6159e6a335"
|
||||
@ -3899,6 +3938,13 @@ escodegen@^1.9.1:
|
||||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-config-prettier@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.3.0.tgz#e73b48e59dc49d950843f3eb96d519e2248286a3"
|
||||
integrity sha512-EWaGjlDAZRzVFveh2Jsglcere2KK5CJBhkNSa1xs3KfMUGdRiT7lG089eqPdvlzWHpAqaekubOsOMu8W8Yk71A==
|
||||
dependencies:
|
||||
get-stdin "^6.0.0"
|
||||
|
||||
eslint-config-standard-jsx@6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-standard-jsx/-/eslint-config-standard-jsx-6.0.2.tgz#90c9aa16ac2c4f8970c13fc7efc608bacd02da70"
|
||||
@ -3954,6 +4000,13 @@ eslint-plugin-node@~7.0.1:
|
||||
resolve "^1.8.1"
|
||||
semver "^5.5.0"
|
||||
|
||||
eslint-plugin-prettier@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.0.tgz#8695188f95daa93b0dc54b249347ca3b79c4686d"
|
||||
integrity sha512-XWX2yVuwVNLOUhQijAkXz+rMPPoCr7WFiAl8ig6I7Xn+pPVhDhzg4DxHpmbeb0iqjO9UronEA3Tb09ChnFVHHA==
|
||||
dependencies:
|
||||
prettier-linter-helpers "^1.0.0"
|
||||
|
||||
eslint-plugin-promise@~4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.0.1.tgz#2d074b653f35a23d1ba89d8e976a985117d1c6a2"
|
||||
@ -3979,14 +4032,77 @@ eslint-scope@^4.0.0:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-scope@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
|
||||
integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
|
||||
dependencies:
|
||||
esrecurse "^4.1.0"
|
||||
estraverse "^4.1.1"
|
||||
|
||||
eslint-utils@^1.3.0, eslint-utils@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512"
|
||||
|
||||
eslint-utils@^1.4.2:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
|
||||
integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
|
||||
eslint-visitor-keys@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||
|
||||
eslint-visitor-keys@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
|
||||
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
|
||||
|
||||
eslint@^6.3.0:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.3.0.tgz#1f1a902f67bfd4c354e7288b81e40654d927eb6a"
|
||||
integrity sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
chalk "^2.1.0"
|
||||
cross-spawn "^6.0.5"
|
||||
debug "^4.0.1"
|
||||
doctrine "^3.0.0"
|
||||
eslint-scope "^5.0.0"
|
||||
eslint-utils "^1.4.2"
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
espree "^6.1.1"
|
||||
esquery "^1.0.1"
|
||||
esutils "^2.0.2"
|
||||
file-entry-cache "^5.0.1"
|
||||
functional-red-black-tree "^1.0.1"
|
||||
glob-parent "^5.0.0"
|
||||
globals "^11.7.0"
|
||||
ignore "^4.0.6"
|
||||
import-fresh "^3.0.0"
|
||||
imurmurhash "^0.1.4"
|
||||
inquirer "^6.4.1"
|
||||
is-glob "^4.0.0"
|
||||
js-yaml "^3.13.1"
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
levn "^0.3.0"
|
||||
lodash "^4.17.14"
|
||||
minimatch "^3.0.4"
|
||||
mkdirp "^0.5.1"
|
||||
natural-compare "^1.4.0"
|
||||
optionator "^0.8.2"
|
||||
progress "^2.0.0"
|
||||
regexpp "^2.0.1"
|
||||
semver "^6.1.2"
|
||||
strip-ansi "^5.2.0"
|
||||
strip-json-comments "^3.0.1"
|
||||
table "^5.2.3"
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
eslint@~5.4.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.4.0.tgz#d068ec03006bb9e06b429dc85f7e46c1b69fac62"
|
||||
@ -4038,6 +4154,15 @@ espree@^4.0.0:
|
||||
acorn-jsx "^5.0.0"
|
||||
eslint-visitor-keys "^1.0.0"
|
||||
|
||||
espree@^6.1.1:
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de"
|
||||
integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==
|
||||
dependencies:
|
||||
acorn "^7.0.0"
|
||||
acorn-jsx "^5.0.2"
|
||||
eslint-visitor-keys "^1.1.0"
|
||||
|
||||
esprima@1.1.x, esprima@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.1.1.tgz#5b6f1547f4d102e670e140c509be6771d6aeb549"
|
||||
@ -4289,6 +4414,15 @@ external-editor@^2.1.0:
|
||||
iconv-lite "^0.4.17"
|
||||
tmp "^0.0.33"
|
||||
|
||||
external-editor@^3.0.3:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495"
|
||||
integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==
|
||||
dependencies:
|
||||
chardet "^0.7.0"
|
||||
iconv-lite "^0.4.24"
|
||||
tmp "^0.0.33"
|
||||
|
||||
extglob@^0.3.1:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1"
|
||||
@ -4337,6 +4471,11 @@ fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
|
||||
fast-diff@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
|
||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||
|
||||
fast-glob@^3.0.3:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.0.4.tgz#d484a41005cb6faeb399b951fd1bd70ddaebb602"
|
||||
@ -4420,6 +4559,13 @@ file-entry-cache@^2.0.0:
|
||||
flat-cache "^1.2.1"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
file-entry-cache@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
|
||||
integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
|
||||
dependencies:
|
||||
flat-cache "^2.0.1"
|
||||
|
||||
filename-regex@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26"
|
||||
@ -4537,6 +4683,20 @@ flat-cache@^1.2.1:
|
||||
rimraf "~2.6.2"
|
||||
write "^0.2.1"
|
||||
|
||||
flat-cache@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
|
||||
integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==
|
||||
dependencies:
|
||||
flatted "^2.0.0"
|
||||
rimraf "2.6.3"
|
||||
write "1.0.3"
|
||||
|
||||
flatted@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
|
||||
integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
|
||||
|
||||
flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd"
|
||||
@ -5260,7 +5420,7 @@ hyperlinker@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/hyperlinker/-/hyperlinker-1.0.0.tgz#23dc9e38a206b208ee49bc2d6c8ef47027df0c0e"
|
||||
|
||||
iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.4:
|
||||
iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
dependencies:
|
||||
@ -5300,9 +5460,10 @@ ignore@^3.0.9:
|
||||
version "3.3.10"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
|
||||
|
||||
ignore@^4.0.2:
|
||||
ignore@^4.0.2, ignore@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
|
||||
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
|
||||
|
||||
ignore@^5.1.1:
|
||||
version "5.1.4"
|
||||
@ -5315,6 +5476,14 @@ import-fresh@^2.0.0:
|
||||
caller-path "^2.0.0"
|
||||
resolve-from "^3.0.0"
|
||||
|
||||
import-fresh@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.1.0.tgz#6d33fa1dcef6df930fae003446f33415af905118"
|
||||
integrity sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==
|
||||
dependencies:
|
||||
parent-module "^1.0.0"
|
||||
resolve-from "^4.0.0"
|
||||
|
||||
import-local@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc"
|
||||
@ -5396,6 +5565,25 @@ inquirer@^5.2.0:
|
||||
strip-ansi "^4.0.0"
|
||||
through "^2.3.6"
|
||||
|
||||
inquirer@^6.4.1:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
|
||||
integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
|
||||
dependencies:
|
||||
ansi-escapes "^3.2.0"
|
||||
chalk "^2.4.2"
|
||||
cli-cursor "^2.1.0"
|
||||
cli-width "^2.0.0"
|
||||
external-editor "^3.0.3"
|
||||
figures "^2.0.0"
|
||||
lodash "^4.17.12"
|
||||
mute-stream "0.0.7"
|
||||
run-async "^2.2.0"
|
||||
rxjs "^6.4.0"
|
||||
string-width "^2.1.0"
|
||||
strip-ansi "^5.1.0"
|
||||
through "^2.3.6"
|
||||
|
||||
internal-ip@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
|
||||
@ -6661,9 +6849,10 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4,
|
||||
version "4.17.13"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.13.tgz#0bdc3a6adc873d2f4e0c4bac285df91b64fc7b93"
|
||||
|
||||
lodash@^4.17.13:
|
||||
lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
log-driver@^1.2.7:
|
||||
version "1.2.7"
|
||||
@ -7699,6 +7888,13 @@ param-case@2.1.x:
|
||||
dependencies:
|
||||
no-case "^2.2.0"
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
parse-asn1@^5.0.0:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8"
|
||||
@ -8062,6 +8258,18 @@ preserve@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
|
||||
|
||||
prettier-linter-helpers@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
|
||||
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@^1.18.2:
|
||||
version "1.18.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
|
||||
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
|
||||
|
||||
pretty-format@^23.6.0:
|
||||
version "23.6.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760"
|
||||
@ -8804,6 +9012,11 @@ resolve-from@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
|
||||
|
||||
resolve-options@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131"
|
||||
@ -8856,7 +9069,7 @@ reusify@^1.0.0:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
|
||||
rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2:
|
||||
rimraf@2, rimraf@2.6.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@~2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
dependencies:
|
||||
@ -8907,6 +9120,13 @@ rxjs@^5.0.0-beta.11, rxjs@^5.5.2:
|
||||
dependencies:
|
||||
symbol-observable "1.0.1"
|
||||
|
||||
rxjs@^6.4.0:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a"
|
||||
integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
safe-buffer@5.1.2, safe-buffer@^5.1.1, safe-buffer@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
@ -9036,6 +9256,11 @@ semver@^6.1.1:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db"
|
||||
|
||||
semver@^6.1.2:
|
||||
version "6.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||
|
||||
semver@~5.3.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f"
|
||||
@ -9199,6 +9424,15 @@ slice-ansi@1.0.0:
|
||||
dependencies:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
slice-ansi@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
|
||||
integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
astral-regex "^1.0.0"
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
|
||||
slugify@^1.3.1:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.3.4.tgz#78d2792d7222b55cd9fc81fa018df99af779efeb"
|
||||
@ -9648,6 +9882,11 @@ strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
|
||||
strip-json-comments@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7"
|
||||
integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==
|
||||
|
||||
subarg@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
|
||||
@ -9708,6 +9947,16 @@ table@^4.0.3:
|
||||
slice-ansi "1.0.0"
|
||||
string-width "^2.1.1"
|
||||
|
||||
table@^5.2.3:
|
||||
version "5.4.6"
|
||||
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
|
||||
integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
|
||||
dependencies:
|
||||
ajv "^6.10.2"
|
||||
lodash "^4.17.14"
|
||||
slice-ansi "^2.1.0"
|
||||
string-width "^3.0.0"
|
||||
|
||||
tapable@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2"
|
||||
@ -10253,6 +10502,11 @@ v8-compile-cache@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.2.tgz#a428b28bb26790734c4fc8bc9fa106fccebf6a6c"
|
||||
|
||||
v8-compile-cache@^2.0.3:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
|
||||
integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
|
||||
@ -10687,6 +10941,13 @@ write-file-atomic@^2.1.0:
|
||||
imurmurhash "^0.1.4"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
write@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
|
||||
integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==
|
||||
dependencies:
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
write@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
|
||||
|
Loading…
x
Reference in New Issue
Block a user