Merge pull request #932 from knsv/i931_code_standard

I931 code standard
This commit is contained in:
Knut Sveidqvist 2019-09-12 22:18:46 +02:00 committed by GitHub
commit 4d1a34661e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 6561 additions and 5868 deletions

18
.eslintrc.json Normal file
View 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
View File

@ -0,0 +1,4 @@
{
"printWidth": 100,
"singleQuote": true
}

View File

@ -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": {

View File

@ -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;

View File

@ -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
}
};

View File

@ -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);
});
});
});

View File

@ -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
}
};

View File

@ -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, '&lt;').replace(/>/g, '&gt;')
txt = txt.replace(/=/g, '&equals;')
txt = txt.replace(/#br#/g, '<br/>')
txt = txt.replace(/<br>/g, '#br#');
txt = txt.replace(/<br\S*?\/>/g, '#br#');
txt = txt.replace(/</g, '&lt;').replace(/>/g, '&gt;');
txt = txt.replace(/=/g, '&equals;');
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
}
};

View File

@ -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
}
};

View File

@ -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

View File

@ -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');
});
});

View File

@ -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;
}
})
});
}
}

View File

@ -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');
});
});
});

View File

@ -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
}
};

View File

@ -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();
}
});
});
});

View File

@ -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
}
};

View File

@ -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();
});
});

View File

@ -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
}
};

View File

@ -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);
});
});

View File

@ -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
}
};

View File

@ -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
}
};

View File

@ -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();
});
});

View File

@ -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
}
};

View File

@ -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
}
};

View File

@ -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

View File

@ -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
}
};

View File

@ -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
}
};

View File

@ -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();
});
});
});

View File

@ -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} : `;
};

View File

@ -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;

View File

@ -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();
});
});
});

View File

@ -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>

View File

@ -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();
});
});
});

View File

@ -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
}
};

View File

@ -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
View File

@ -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"