fixed class diagram hopefuly no conflict

This commit is contained in:
Dan Shai
2019-06-14 17:29:40 +03:00
68 changed files with 3385 additions and 655 deletions

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ let vertices = {}
let edges = []
let classes = []
let subGraphs = []
let subGraphLookup = {}
let tooltips = {}
let subCount = 0
let direction
@@ -18,8 +19,9 @@ let funs = []
* @param text
* @param type
* @param style
* @param classes
*/
export const addVertex = function (id, text, type, style) {
export const addVertex = function (id, text, type, style, classes) {
let txt
if (typeof id === 'undefined') {
@@ -52,6 +54,13 @@ export const addVertex = function (id, text, type, style) {
})
}
}
if (typeof classes !== 'undefined') {
if (classes !== null) {
classes.forEach(function (s) {
vertices[id].classes.push(s)
})
}
}
}
/**
@@ -87,12 +96,14 @@ export const addLink = function (start, end, type, linktext) {
* @param pos
* @param interpolate
*/
export const updateLinkInterpolate = function (pos, interp) {
if (pos === 'default') {
edges.defaultInterpolate = interp
} else {
edges[pos].interpolate = interp
}
export const updateLinkInterpolate = function (positions, interp) {
positions.forEach(function (pos) {
if (pos === 'default') {
edges.defaultInterpolate = interp
} else {
edges[pos].interpolate = interp
}
})
}
/**
@@ -100,15 +111,17 @@ export const updateLinkInterpolate = function (pos, interp) {
* @param pos
* @param style
*/
export const updateLink = function (pos, style) {
if (pos === 'default') {
edges.defaultStyle = style
} else {
if (utils.isSubstringInArray('fill', style) === -1) {
style.push('fill:none')
export const updateLink = function (positions, style) {
positions.forEach(function (pos) {
if (pos === 'default') {
edges.defaultStyle = style
} else {
if (utils.isSubstringInArray('fill', style) === -1) {
style.push('fill:none')
}
edges[pos].style = style
}
edges[pos].style = style
}
})
}
export const addClass = function (id, style) {
@@ -143,6 +156,10 @@ export const setClass = function (ids, className) {
if (typeof vertices[id] !== 'undefined') {
vertices[id].classes.push(className)
}
if (typeof subGraphLookup[id] !== 'undefined') {
subGraphLookup[id].classes.push(className)
}
})
}
@@ -283,6 +300,7 @@ export const clear = function () {
funs = []
funs.push(setupToolTips)
subGraphs = []
subGraphLookup = {}
subCount = 0
tooltips = []
}
@@ -297,7 +315,7 @@ export const defaultStyle = function () {
/**
* Clears the internal graph db so that a new graph can be parsed.
*/
export const addSubGraph = function (list, title) {
export const addSubGraph = function (id, list, title) {
function uniq (a) {
const prims = { 'boolean': {}, 'number': {}, 'string': {} }
const objs = []
@@ -315,10 +333,13 @@ export const addSubGraph = function (list, title) {
nodeList = uniq(nodeList.concat.apply(nodeList, list))
const subGraph = { id: 'subGraph' + subCount, nodes: nodeList, title: title.trim() }
subGraphs.push(subGraph)
id = id || ('subGraph' + subCount)
title = title || ''
subCount = subCount + 1
return subGraph.id
const subGraph = { id: id, nodes: nodeList, title: title.trim(), classes: [] }
subGraphs.push(subGraph)
subGraphLookup[id] = subGraph
return id
}
const getPosForId = function (id) {

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -216,7 +216,7 @@ export const getTextObj = function () {
const txt = {
x: 0,
y: 0,
'fill': 'black',
'fill': undefined,
'text-anchor': 'start',
style: '#666',
width: 100,

View File

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

View File

@@ -447,6 +447,7 @@ const render = function (id, txt, cb, container) {
if (typeof cb !== 'undefined') {
cb(svgCode, flowDb.bindFunctions)
cb(svgCode, ganttDb.bindFunctions)
} else {
logger.warn('CB = undefined!')
}

View File

@@ -27,7 +27,8 @@ $signalColor: $mainContrastColor;
$signalTextColor: $mainContrastColor;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: $mainContrastColor;
$labelTextColor: $darkTextColor;
$loopTextColor: $mainContrastColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
@@ -43,6 +44,7 @@ $taskBkgColor: $mainBkg;
$taskTextColor: $darkTextColor;
$taskTextLightColor: $mainContrastColor;
$taskTextOutsideColor: $taskTextLightColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: rgba(255, 255, 255, 0.5);
$activeTaskBkgColor: #81B1DB;
$gridColor: $mainContrastColor;

View File

@@ -26,6 +26,7 @@ $signalTextColor: #333;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: $actorTextColor;
$loopTextColor: $actorTextColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
@@ -42,6 +43,7 @@ $taskTextLightColor: white;
$taskTextColor: $taskTextLightColor;
$taskTextDarkColor: black;
$taskTextOutsideColor: $taskTextDarkColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: #534fbc;
$activeTaskBkgColor: #bfc7ff;
$gridColor: lightgrey;

View File

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

View File

@@ -27,6 +27,7 @@ $signalTextColor: #333;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: #326932;
$labelTextColor: $actorTextColor;
$loopTextColor: $actorTextColor;
$noteBorderColor: $border2;
$noteBkgColor: #fff5ad;
$activationBorderColor: #666;
@@ -43,6 +44,7 @@ $taskTextLightColor: white;
$taskTextColor: $taskTextLightColor;
$taskTextDarkColor: black;
$taskTextOutsideColor: $taskTextDarkColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: $taskBorderColor;
$activeTaskBkgColor: $mainBkg;
$gridColor: lightgrey;

View File

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

View File

@@ -30,7 +30,8 @@ $signalColor: $text;
$signalTextColor: $text;
$labelBoxBkgColor: $actorBkg;
$labelBoxBorderColor: $actorBorder;
$labelTextColor: white;
$labelTextColor: $text;
$loopTextColor: $text;
$noteBorderColor: darken($note, 60%);
$noteBkgColor: $note;
$activationBorderColor: #666;
@@ -47,6 +48,7 @@ $taskTextLightColor: white;
$taskTextColor: $taskTextLightColor;
$taskTextDarkColor: $text;
$taskTextOutsideColor: $taskTextDarkColor;
$taskTextClickableColor: #003163;
$activeTaskBorderColor: $taskBorderColor;
$activeTaskBkgColor: $mainBkg;
$gridColor: lighten($border1, 30%);

View File

@@ -49,7 +49,7 @@ text.actor {
}
.loopText {
fill: $labelTextColor;
fill: $loopTextColor;
stroke: none;
}