1423 lines
44 KiB
JavaScript
1423 lines
44 KiB
JavaScript
|
// flowchart, v1.4.0
|
||
|
// Copyright (c)2015 Adriano Raiano (adrai).
|
||
|
// Distributed under MIT license
|
||
|
// http://adrai.github.io/flowchart.js
|
||
|
(function() {
|
||
|
|
||
|
// add indexOf to non ECMA-262 standard compliant browsers
|
||
|
if (!Array.prototype.indexOf) {
|
||
|
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
|
||
|
"use strict";
|
||
|
if (this === null) {
|
||
|
throw new TypeError();
|
||
|
}
|
||
|
var t = Object(this);
|
||
|
var len = t.length >>> 0;
|
||
|
if (len === 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
var n = 0;
|
||
|
if (arguments.length > 0) {
|
||
|
n = Number(arguments[1]);
|
||
|
if (n != n) { // shortcut for verifying if it's NaN
|
||
|
n = 0;
|
||
|
} else if (n !== 0 && n != Infinity && n != -Infinity) {
|
||
|
n = (n > 0 || -1) * Math.floor(Math.abs(n));
|
||
|
}
|
||
|
}
|
||
|
if (n >= len) {
|
||
|
return -1;
|
||
|
}
|
||
|
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
|
||
|
for (; k < len; k++) {
|
||
|
if (k in t && t[k] === searchElement) {
|
||
|
return k;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// add lastIndexOf to non ECMA-262 standard compliant browsers
|
||
|
if (!Array.prototype.lastIndexOf) {
|
||
|
Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/) {
|
||
|
"use strict";
|
||
|
if (this === null) {
|
||
|
throw new TypeError();
|
||
|
}
|
||
|
var t = Object(this);
|
||
|
var len = t.length >>> 0;
|
||
|
if (len === 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
var n = len;
|
||
|
if (arguments.length > 1) {
|
||
|
n = Number(arguments[1]);
|
||
|
if (n != n) {
|
||
|
n = 0;
|
||
|
} else if (n !== 0 && n != (1 / 0) && n != -(1 / 0)) {
|
||
|
n = (n > 0 || -1) * Math.floor(Math.abs(n));
|
||
|
}
|
||
|
}
|
||
|
var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n);
|
||
|
for (; k >= 0; k--) {
|
||
|
if (k in t && t[k] === searchElement) {
|
||
|
return k;
|
||
|
}
|
||
|
}
|
||
|
return -1;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
if (!String.prototype.trim) {
|
||
|
String.prototype.trim = function() {
|
||
|
return this.replace(/^\s+|\s+$/g, '');
|
||
|
};
|
||
|
}
|
||
|
|
||
|
var root = this,
|
||
|
flowchart = {};
|
||
|
|
||
|
// Export the flowchart object for **CommonJS**.
|
||
|
// If we're not in CommonJS, add `flowchart` to the
|
||
|
// global object or to jquery.
|
||
|
if (typeof module !== 'undefined' && module.exports) {
|
||
|
module.exports = flowchart;
|
||
|
} else {
|
||
|
root.flowchart = root.flowchart || flowchart;
|
||
|
}
|
||
|
// defaults
|
||
|
var o = {
|
||
|
'x': 0,
|
||
|
'y': 0,
|
||
|
'line-width': 3,
|
||
|
'line-length': 50,
|
||
|
'text-margin': 10,
|
||
|
'font-size': 14,
|
||
|
'font-color': 'black',
|
||
|
// 'font': 'normal',
|
||
|
// 'font-family': 'calibri',
|
||
|
// 'font-weight': 'normal',
|
||
|
'line-color': 'black',
|
||
|
'element-color': 'black',
|
||
|
'fill': 'white',
|
||
|
'yes-text': 'yes',
|
||
|
'no-text': 'no',
|
||
|
'arrow-end': 'block',
|
||
|
'class': 'flowchart',
|
||
|
'scale': 1,
|
||
|
'symbols': {
|
||
|
'start': {},
|
||
|
'end': {},
|
||
|
'condition': {},
|
||
|
'inputoutput': {},
|
||
|
'operation': {},
|
||
|
'subroutine': {}
|
||
|
}//,
|
||
|
// 'flowstate' : {
|
||
|
// 'past' : { 'fill': '#CCCCCC', 'font-size': 12},
|
||
|
// 'current' : {'fill': 'yellow', 'font-color': 'red', 'font-weight': 'bold'},
|
||
|
// 'future' : { 'fill': '#FFFF99'},
|
||
|
// 'invalid': {'fill': '#444444'}
|
||
|
// }
|
||
|
};
|
||
|
function _defaults(options, defaultOptions) {
|
||
|
if (!options || typeof options === 'function') {
|
||
|
return defaultOptions;
|
||
|
}
|
||
|
|
||
|
var merged = {};
|
||
|
for (var attrname in defaultOptions) {
|
||
|
merged[attrname] = defaultOptions[attrname];
|
||
|
}
|
||
|
|
||
|
for (attrname in options) {
|
||
|
if (options[attrname]) {
|
||
|
if (typeof merged[attrname] === 'object') {
|
||
|
merged[attrname] = _defaults(merged[attrname], options[attrname]);
|
||
|
} else {
|
||
|
merged[attrname] = options[attrname];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return merged;
|
||
|
}
|
||
|
|
||
|
function _inherits(ctor, superCtor) {
|
||
|
if (typeof(Object.create) === 'function') {
|
||
|
// implementation from standard node.js 'util' module
|
||
|
ctor.super_ = superCtor;
|
||
|
ctor.prototype = Object.create(superCtor.prototype, {
|
||
|
constructor: {
|
||
|
value: ctor,
|
||
|
enumerable: false,
|
||
|
writable: true,
|
||
|
configurable: true
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
// old school shim for old browsers
|
||
|
ctor.super_ = superCtor;
|
||
|
var TempCtor = function () {};
|
||
|
TempCtor.prototype = superCtor.prototype;
|
||
|
ctor.prototype = new TempCtor();
|
||
|
ctor.prototype.constructor = ctor;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// move dependent functions to a container so that
|
||
|
// they can be overriden easier in no jquery environment (node.js)
|
||
|
var f = {
|
||
|
defaults: _defaults,
|
||
|
inherits: _inherits
|
||
|
};
|
||
|
function drawPath(chart, location, points) {
|
||
|
var i, len;
|
||
|
var path = 'M{0},{1}';
|
||
|
for (i = 2, len = 2 * points.length + 2; i < len; i+=2) {
|
||
|
path += ' L{' + i + '},{' + (i + 1) + '}';
|
||
|
}
|
||
|
var pathValues = [location.x, location.y];
|
||
|
for (i = 0, len = points.length; i < len; i++) {
|
||
|
pathValues.push(points[i].x);
|
||
|
pathValues.push(points[i].y);
|
||
|
}
|
||
|
var symbol = chart.paper.path(path, pathValues);
|
||
|
symbol.attr('stroke', chart.options['element-color']);
|
||
|
symbol.attr('stroke-width', chart.options['line-width']);
|
||
|
|
||
|
var font = chart.options['font'];
|
||
|
var fontF = chart.options['font-family'];
|
||
|
var fontW = chart.options['font-weight'];
|
||
|
|
||
|
if (font) symbol.attr({ 'font': font });
|
||
|
if (fontF) symbol.attr({ 'font-family': fontF });
|
||
|
if (fontW) symbol.attr({ 'font-weight': fontW });
|
||
|
|
||
|
return symbol;
|
||
|
}
|
||
|
|
||
|
function drawLine(chart, from, to, text) {
|
||
|
var i, len;
|
||
|
|
||
|
if (Object.prototype.toString.call(to) !== '[object Array]') {
|
||
|
to = [to];
|
||
|
}
|
||
|
|
||
|
var path = 'M{0},{1}';
|
||
|
for (i = 2, len = 2 * to.length + 2; i < len; i+=2) {
|
||
|
path += ' L{' + i + '},{' + (i + 1) + '}';
|
||
|
}
|
||
|
var pathValues = [from.x, from.y];
|
||
|
for (i = 0, len = to.length; i < len; i++) {
|
||
|
pathValues.push(to[i].x);
|
||
|
pathValues.push(to[i].y);
|
||
|
}
|
||
|
|
||
|
var line = chart.paper.path(path, pathValues);
|
||
|
line.attr({
|
||
|
stroke: chart.options['line-color'],
|
||
|
'stroke-width': chart.options['line-width'],
|
||
|
'arrow-end': chart.options['arrow-end']
|
||
|
});
|
||
|
|
||
|
var font = chart.options['font'];
|
||
|
var fontF = chart.options['font-family'];
|
||
|
var fontW = chart.options['font-weight'];
|
||
|
|
||
|
if (font) line.attr({ 'font': font });
|
||
|
if (fontF) line.attr({ 'font-family': fontF });
|
||
|
if (fontW) line.attr({ 'font-weight': fontW });
|
||
|
|
||
|
if (text) {
|
||
|
|
||
|
var centerText = false;
|
||
|
|
||
|
var textPath = chart.paper.text(0, 0, text);
|
||
|
|
||
|
var isHorizontal = false;
|
||
|
var firstTo = to[0];
|
||
|
|
||
|
if (from.y === firstTo.y) {
|
||
|
isHorizontal = true;
|
||
|
}
|
||
|
|
||
|
var x = 0,
|
||
|
y = 0;
|
||
|
|
||
|
if (centerText) {
|
||
|
if (from.x > firstTo.x) {
|
||
|
x = from.x - (from.x - firstTo.x)/2;
|
||
|
} else {
|
||
|
x = firstTo.x - (firstTo.x - from.x)/2;
|
||
|
}
|
||
|
|
||
|
if (from.y > firstTo.y) {
|
||
|
y = from.y - (from.y - firstTo.y)/2;
|
||
|
} else {
|
||
|
y = firstTo.y - (firstTo.y - from.y)/2;
|
||
|
}
|
||
|
|
||
|
if (isHorizontal) {
|
||
|
x -= textPath.getBBox().width/2;
|
||
|
y -= chart.options['text-margin'];
|
||
|
} else {
|
||
|
x += chart.options['text-margin'];
|
||
|
y -= textPath.getBBox().height/2;
|
||
|
}
|
||
|
} else {
|
||
|
x = from.x;
|
||
|
y = from.y;
|
||
|
|
||
|
if (isHorizontal) {
|
||
|
x += chart.options['text-margin']/2;
|
||
|
y -= chart.options['text-margin'];
|
||
|
} else {
|
||
|
x += chart.options['text-margin']/2;
|
||
|
y += chart.options['text-margin'];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
textPath.attr({
|
||
|
'text-anchor': 'start',
|
||
|
'font-size': chart.options['font-size'],
|
||
|
'fill': chart.options['font-color'],
|
||
|
x: x,
|
||
|
y: y
|
||
|
});
|
||
|
|
||
|
if (font) textPath.attr({ 'font': font });
|
||
|
if (fontF) textPath.attr({ 'font-family': fontF });
|
||
|
if (fontW) textPath.attr({ 'font-weight': fontW });
|
||
|
}
|
||
|
|
||
|
return line;
|
||
|
}
|
||
|
|
||
|
function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
|
||
|
// if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
|
||
|
var denominator, a, b, numerator1, numerator2, result = {
|
||
|
x: null,
|
||
|
y: null,
|
||
|
onLine1: false,
|
||
|
onLine2: false
|
||
|
};
|
||
|
denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
|
||
|
if (denominator === 0) {
|
||
|
return result;
|
||
|
}
|
||
|
a = line1StartY - line2StartY;
|
||
|
b = line1StartX - line2StartX;
|
||
|
numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
|
||
|
numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
|
||
|
a = numerator1 / denominator;
|
||
|
b = numerator2 / denominator;
|
||
|
|
||
|
// if we cast these lines infinitely in both directions, they intersect here:
|
||
|
result.x = line1StartX + (a * (line1EndX - line1StartX));
|
||
|
result.y = line1StartY + (a * (line1EndY - line1StartY));
|
||
|
/*
|
||
|
// it is worth noting that this should be the same as:
|
||
|
x = line2StartX + (b * (line2EndX - line2StartX));
|
||
|
y = line2StartX + (b * (line2EndY - line2StartY));
|
||
|
*/
|
||
|
// if line1 is a segment and line2 is infinite, they intersect if:
|
||
|
if (a > 0 && a < 1) {
|
||
|
result.onLine1 = true;
|
||
|
}
|
||
|
// if line2 is a segment and line1 is infinite, they intersect if:
|
||
|
if (b > 0 && b < 1) {
|
||
|
result.onLine2 = true;
|
||
|
}
|
||
|
// if line1 and line2 are segments, they intersect if both of the above are true
|
||
|
return result;
|
||
|
}
|
||
|
function FlowChart(container, options) {
|
||
|
options = options || {};
|
||
|
|
||
|
this.paper = new Raphael(container);
|
||
|
|
||
|
this.options = f.defaults(options, o);
|
||
|
|
||
|
this.symbols = [];
|
||
|
this.lines = [];
|
||
|
this.start = null;
|
||
|
}
|
||
|
|
||
|
FlowChart.prototype.handle = function(symbol) {
|
||
|
if (this.symbols.indexOf(symbol) <= -1) {
|
||
|
this.symbols.push(symbol);
|
||
|
}
|
||
|
|
||
|
var flowChart = this;
|
||
|
|
||
|
if (symbol instanceof(Condition)) {
|
||
|
symbol.yes = function(nextSymbol) {
|
||
|
symbol.yes_symbol = nextSymbol;
|
||
|
if(symbol.no_symbol) {
|
||
|
symbol.pathOk = true;
|
||
|
}
|
||
|
return flowChart.handle(nextSymbol);
|
||
|
};
|
||
|
symbol.no = function(nextSymbol) {
|
||
|
symbol.no_symbol = nextSymbol;
|
||
|
if(symbol.yes_symbol) {
|
||
|
symbol.pathOk = true;
|
||
|
}
|
||
|
return flowChart.handle(nextSymbol);
|
||
|
};
|
||
|
} else {
|
||
|
symbol.then = function(nextSymbol) {
|
||
|
symbol.next = nextSymbol;
|
||
|
symbol.pathOk = true;
|
||
|
return flowChart.handle(nextSymbol);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return symbol;
|
||
|
};
|
||
|
|
||
|
FlowChart.prototype.startWith = function(symbol) {
|
||
|
this.start = symbol;
|
||
|
return this.handle(symbol);
|
||
|
};
|
||
|
|
||
|
FlowChart.prototype.render = function() {
|
||
|
var maxWidth = 0,
|
||
|
maxHeight = 0,
|
||
|
i = 0,
|
||
|
len = 0,
|
||
|
maxX = 0,
|
||
|
maxY = 0,
|
||
|
symbol;
|
||
|
|
||
|
for (i = 0, len = this.symbols.length; i < len; i++) {
|
||
|
symbol = this.symbols[i];
|
||
|
if (symbol.width > maxWidth) {
|
||
|
maxWidth = symbol.width;
|
||
|
}
|
||
|
if (symbol.height > maxHeight) {
|
||
|
maxHeight = symbol.height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (i = 0, len = this.symbols.length; i < len; i++) {
|
||
|
symbol = this.symbols[i];
|
||
|
symbol.shiftX(this.options.x + (maxWidth - symbol.width)/2 + this.options['line-width']);
|
||
|
symbol.shiftY(this.options.y + (maxHeight - symbol.height)/2 + this.options['line-width']);
|
||
|
}
|
||
|
|
||
|
this.start.render();
|
||
|
// for (i = 0, len = this.symbols.length; i < len; i++) {
|
||
|
// symbol = this.symbols[i];
|
||
|
// symbol.render();
|
||
|
// }
|
||
|
|
||
|
for (i = 0, len = this.symbols.length; i < len; i++) {
|
||
|
symbol = this.symbols[i];
|
||
|
symbol.renderLines();
|
||
|
}
|
||
|
|
||
|
maxX = this.maxXFromLine;
|
||
|
|
||
|
for (i = 0, len = this.symbols.length; i < len; i++) {
|
||
|
symbol = this.symbols[i];
|
||
|
var x = symbol.getX() + symbol.width;
|
||
|
var y = symbol.getY() + symbol.height;
|
||
|
if (x > maxX) {
|
||
|
maxX = x;
|
||
|
}
|
||
|
if (y > maxY) {
|
||
|
maxY = y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var scale = this.options['scale'];
|
||
|
var lineWidth = this.options['line-width'];
|
||
|
this.paper.setSize((maxX * scale) + (lineWidth * scale), (maxY * scale) + (lineWidth * scale));
|
||
|
this.paper.setViewBox(0, 0, maxX + lineWidth, maxY + lineWidth, true);
|
||
|
};
|
||
|
|
||
|
FlowChart.prototype.clean = function() {
|
||
|
if (this.paper) {
|
||
|
var paperDom = this.paper.canvas;
|
||
|
paperDom.parentNode.removeChild(paperDom);
|
||
|
}
|
||
|
};
|
||
|
function Symbol(chart, options, symbol) {
|
||
|
this.chart = chart;
|
||
|
this.group = this.chart.paper.set();
|
||
|
this.symbol = symbol;
|
||
|
this.connectedTo = [];
|
||
|
this.symbolType = options.symbolType;
|
||
|
this.flowstate = (options.flowstate || 'future');
|
||
|
|
||
|
this.next_direction = options.next && options['direction_next'] ? options['direction_next'] : undefined;
|
||
|
|
||
|
this.text = this.chart.paper.text(0, 0, options.text);
|
||
|
//Raphael does not support the svg group tag so setting the text node id to the symbol node id plus t
|
||
|
if (options.key) { this.text.node.id = options.key + 't'; }
|
||
|
this.text.node.setAttribute('class', this.getAttr('class') + 't');
|
||
|
|
||
|
this.text.attr({
|
||
|
'text-anchor': 'start',
|
||
|
'x' : this.getAttr('text-margin'),
|
||
|
'fill' : this.getAttr('font-color'),
|
||
|
'font-size' : this.getAttr('font-size')
|
||
|
});
|
||
|
|
||
|
var font = this.getAttr('font');
|
||
|
var fontF = this.getAttr('font-family');
|
||
|
var fontW = this.getAttr('font-weight');
|
||
|
|
||
|
if (font) this.text.attr({ 'font': font });
|
||
|
if (fontF) this.text.attr({ 'font-family': fontF });
|
||
|
if (fontW) this.text.attr({ 'font-weight': fontW });
|
||
|
|
||
|
if (options.link) { this.text.attr('href', options.link); }
|
||
|
if (options.target) { this.text.attr('target', options.target); }
|
||
|
|
||
|
var maxWidth = this.getAttr('maxWidth');
|
||
|
if (maxWidth) {
|
||
|
// using this approach: http://stackoverflow.com/a/3153457/22466
|
||
|
var words = options.text.split(' ');
|
||
|
var tempText = "";
|
||
|
for (var i=0, ii=words.length; i<ii; i++) {
|
||
|
var word = words[i];
|
||
|
this.text.attr("text", tempText + " " + word);
|
||
|
if (this.text.getBBox().width > maxWidth) {
|
||
|
tempText += "\n" + word;
|
||
|
} else {
|
||
|
tempText += " " + word;
|
||
|
}
|
||
|
}
|
||
|
this.text.attr("text", tempText.substring(1));
|
||
|
}
|
||
|
|
||
|
this.group.push(this.text);
|
||
|
|
||
|
if (symbol) {
|
||
|
var tmpMargin = this.getAttr('text-margin');
|
||
|
|
||
|
symbol.attr({
|
||
|
'fill' : this.getAttr('fill'),
|
||
|
'stroke' : this.getAttr('element-color'),
|
||
|
'stroke-width' : this.getAttr('line-width'),
|
||
|
'width' : this.text.getBBox().width + 2 * tmpMargin,
|
||
|
'height' : this.text.getBBox().height + 2 * tmpMargin
|
||
|
});
|
||
|
|
||
|
symbol.node.setAttribute('class', this.getAttr('class'));
|
||
|
|
||
|
if (options.link) { symbol.attr('href', options.link); }
|
||
|
if (options.target) { symbol.attr('target', options.target); }
|
||
|
if (options.key) { symbol.node.id = options.key; }
|
||
|
|
||
|
this.group.push(symbol);
|
||
|
symbol.insertBefore(this.text);
|
||
|
|
||
|
this.text.attr({
|
||
|
'y': symbol.getBBox().height/2
|
||
|
});
|
||
|
|
||
|
this.initialize();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/* Gets the attribute based on Flowstate, Symbol-Name and default, first found wins */
|
||
|
Symbol.prototype.getAttr = function(attName) {
|
||
|
if (!this.chart) {
|
||
|
return undefined;
|
||
|
}
|
||
|
var opt3 = (this.chart.options) ? this.chart.options[attName] : undefined;
|
||
|
var opt2 = (this.chart.options.symbols) ? this.chart.options.symbols[this.symbolType][attName] : undefined;
|
||
|
var opt1;
|
||
|
if (this.chart.options.flowstate && this.chart.options.flowstate[this.flowstate]) {
|
||
|
opt1 = this.chart.options.flowstate[this.flowstate][attName];
|
||
|
}
|
||
|
return (opt1 || opt2 || opt3);
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.initialize = function() {
|
||
|
this.group.transform('t' + this.getAttr('line-width') + ',' + this.getAttr('line-width'));
|
||
|
|
||
|
this.width = this.group.getBBox().width;
|
||
|
this.height = this.group.getBBox().height;
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.getCenter = function() {
|
||
|
return {x: this.getX() + this.width/2,
|
||
|
y: this.getY() + this.height/2};
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.getX = function() {
|
||
|
return this.group.getBBox().x;
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.getY = function() {
|
||
|
return this.group.getBBox().y;
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.shiftX = function(x) {
|
||
|
this.group.transform('t' + (this.getX() + x) + ',' + this.getY());
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.setX = function(x) {
|
||
|
this.group.transform('t' + x + ',' + this.getY());
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.shiftY = function(y) {
|
||
|
this.group.transform('t' + this.getX() + ',' + (this.getY() + y));
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.setY = function(y) {
|
||
|
this.group.transform('t' + this.getX() + ',' + y);
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.getTop = function() {
|
||
|
var y = this.getY();
|
||
|
var x = this.getX() + this.width/2;
|
||
|
return {x: x, y: y};
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.getBottom = function() {
|
||
|
var y = this.getY() + this.height;
|
||
|
var x = this.getX() + this.width/2;
|
||
|
return {x: x, y: y};
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.getLeft = function() {
|
||
|
var y = this.getY() + this.group.getBBox().height/2;
|
||
|
var x = this.getX();
|
||
|
return {x: x, y: y};
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.getRight = function() {
|
||
|
var y = this.getY() + this.group.getBBox().height/2;
|
||
|
var x = this.getX() + this.group.getBBox().width;
|
||
|
return {x: x, y: y};
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.render = function() {
|
||
|
if (this.next) {
|
||
|
|
||
|
var lineLength = this.getAttr('line-length');
|
||
|
|
||
|
if (this.next_direction === 'right') {
|
||
|
|
||
|
var rightPoint = this.getRight();
|
||
|
var leftPoint = this.next.getLeft();
|
||
|
|
||
|
if (!this.next.isPositioned) {
|
||
|
|
||
|
this.next.setY(rightPoint.y - this.next.height/2);
|
||
|
this.next.shiftX(this.group.getBBox().x + this.width + lineLength);
|
||
|
|
||
|
var self = this;
|
||
|
(function shift() {
|
||
|
var hasSymbolUnder = false;
|
||
|
var symb;
|
||
|
for (var i = 0, len = self.chart.symbols.length; i < len; i++) {
|
||
|
symb = self.chart.symbols[i];
|
||
|
|
||
|
var diff = Math.abs(symb.getCenter().x - self.next.getCenter().x);
|
||
|
if (symb.getCenter().y > self.next.getCenter().y && diff <= self.next.width/2) {
|
||
|
hasSymbolUnder = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hasSymbolUnder) {
|
||
|
self.next.setX(symb.getX() + symb.width + lineLength);
|
||
|
shift();
|
||
|
}
|
||
|
})();
|
||
|
|
||
|
this.next.isPositioned = true;
|
||
|
|
||
|
this.next.render();
|
||
|
}
|
||
|
} else {
|
||
|
var bottomPoint = this.getBottom();
|
||
|
var topPoint = this.next.getTop();
|
||
|
|
||
|
if (!this.next.isPositioned) {
|
||
|
this.next.shiftY(this.getY() + this.height + lineLength);
|
||
|
this.next.setX(bottomPoint.x - this.next.width/2);
|
||
|
this.next.isPositioned = true;
|
||
|
|
||
|
this.next.render();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.renderLines = function() {
|
||
|
if (this.next) {
|
||
|
if (this.next_direction) {
|
||
|
this.drawLineTo(this.next, '', this.next_direction);
|
||
|
} else {
|
||
|
this.drawLineTo(this.next);
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Symbol.prototype.drawLineTo = function(symbol, text, origin) {
|
||
|
if (this.connectedTo.indexOf(symbol) < 0) {
|
||
|
this.connectedTo.push(symbol);
|
||
|
}
|
||
|
|
||
|
var x = this.getCenter().x,
|
||
|
y = this.getCenter().y,
|
||
|
top = this.getTop(),
|
||
|
right = this.getRight(),
|
||
|
bottom = this.getBottom(),
|
||
|
left = this.getLeft();
|
||
|
|
||
|
var symbolX = symbol.getCenter().x,
|
||
|
symbolY = symbol.getCenter().y,
|
||
|
symbolTop = symbol.getTop(),
|
||
|
symbolRight = symbol.getRight(),
|
||
|
symbolBottom = symbol.getBottom(),
|
||
|
symbolLeft = symbol.getLeft();
|
||
|
|
||
|
var isOnSameColumn = x === symbolX,
|
||
|
isOnSameLine = y === symbolY,
|
||
|
isUnder = y < symbolY,
|
||
|
isUpper = y > symbolY,
|
||
|
isLeft = x > symbolX,
|
||
|
isRight = x < symbolX;
|
||
|
|
||
|
var maxX = 0,
|
||
|
line,
|
||
|
lineLength = this.getAttr('line-length'),
|
||
|
lineWith = this.getAttr('line-width');
|
||
|
|
||
|
if ((!origin || origin === 'bottom') && isOnSameColumn && isUnder) {
|
||
|
line = drawLine(this.chart, bottom, symbolTop, text);
|
||
|
this.bottomStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = bottom.x;
|
||
|
} else if ((!origin || origin === 'right') && isOnSameLine && isRight) {
|
||
|
line = drawLine(this.chart, right, symbolLeft, text);
|
||
|
this.rightStart = true;
|
||
|
symbol.leftEnd = true;
|
||
|
maxX = symbolLeft.x;
|
||
|
} else if ((!origin || origin === 'left') && isOnSameLine && isLeft) {
|
||
|
line = drawLine(this.chart, left, symbolRight, text);
|
||
|
this.leftStart = true;
|
||
|
symbol.rightEnd = true;
|
||
|
maxX = symbolRight.x;
|
||
|
} else if ((!origin || origin === 'right') && isOnSameColumn && isUpper) {
|
||
|
line = drawLine(this.chart, right, [
|
||
|
{x: right.x + lineLength/2, y: right.y},
|
||
|
{x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.rightStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = right.x + lineLength/2;
|
||
|
} else if ((!origin || origin === 'right') && isOnSameColumn && isUnder) {
|
||
|
line = drawLine(this.chart, right, [
|
||
|
{x: right.x + lineLength/2, y: right.y},
|
||
|
{x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.rightStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = right.x + lineLength/2;
|
||
|
} else if ((!origin || origin === 'bottom') && isLeft) {
|
||
|
if (this.leftEnd && isUpper) {
|
||
|
line = drawLine(this.chart, bottom, [
|
||
|
{x: bottom.x, y: bottom.y + lineLength/2},
|
||
|
{x: bottom.x + (bottom.x - symbolTop.x)/2, y: bottom.y + lineLength/2},
|
||
|
{x: bottom.x + (bottom.x - symbolTop.x)/2, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
} else {
|
||
|
line = drawLine(this.chart, bottom, [
|
||
|
{x: bottom.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
}
|
||
|
this.bottomStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = bottom.x + (bottom.x - symbolTop.x)/2;
|
||
|
} else if ((!origin || origin === 'bottom') && isRight) {
|
||
|
line = drawLine(this.chart, bottom, [
|
||
|
{x: bottom.x, y: bottom.y + lineLength/2},
|
||
|
{x: bottom.x + (bottom.x - symbolTop.x)/2, y: bottom.y + lineLength/2},
|
||
|
{x: bottom.x + (bottom.x - symbolTop.x)/2, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.bottomStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = bottom.x + (bottom.x - symbolTop.x)/2;
|
||
|
} else if ((origin && origin === 'right') && isLeft) {
|
||
|
line = drawLine(this.chart, right, [
|
||
|
{x: right.x + lineLength/2, y: right.y},
|
||
|
{x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.rightStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = right.x + lineLength/2;
|
||
|
} else if ((origin && origin === 'right') && isRight) {
|
||
|
line = drawLine(this.chart, right, [
|
||
|
{x: symbolTop.x, y: right.y},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.rightStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = right.x + lineLength/2;
|
||
|
} else if ((origin && origin === 'bottom') && isOnSameColumn && isUpper) {
|
||
|
line = drawLine(this.chart, bottom, [
|
||
|
{x: bottom.x, y: bottom.y + lineLength/2},
|
||
|
{x: right.x + lineLength/2, y: bottom.y + lineLength/2},
|
||
|
{x: right.x + lineLength/2, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.bottomStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = bottom.x + lineLength/2;
|
||
|
} else if ((origin === 'left') && isOnSameColumn && isUpper) {
|
||
|
var diffX = left.x - lineLength/2;
|
||
|
if (symbolLeft.x < left.x) {
|
||
|
diffX = symbolLeft.x - lineLength/2;
|
||
|
}
|
||
|
line = drawLine(this.chart, left, [
|
||
|
{x: diffX, y: left.y},
|
||
|
{x: diffX, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.leftStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = left.x;
|
||
|
} else if ((origin === 'left')) {
|
||
|
line = drawLine(this.chart, left, [
|
||
|
{x: symbolTop.x + (left.x - symbolTop.x)/ 2, y: left.y},
|
||
|
{x: symbolTop.x + (left.x - symbolTop.x)/ 2, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y - lineLength/2},
|
||
|
{x: symbolTop.x, y: symbolTop.y}
|
||
|
], text);
|
||
|
this.leftStart = true;
|
||
|
symbol.topEnd = true;
|
||
|
maxX = left.x;
|
||
|
}
|
||
|
|
||
|
if (line) {
|
||
|
var self = this;
|
||
|
for (var l = 0, llen = this.chart.lines.length; l < llen; l++) {
|
||
|
var otherLine = this.chart.lines[l];
|
||
|
var i,
|
||
|
len,
|
||
|
intersections,
|
||
|
inter;
|
||
|
|
||
|
var ePath = otherLine.attr('path'),
|
||
|
lPath = line.attr('path');
|
||
|
|
||
|
for (var iP = 0, lenP = ePath.length - 1; iP < lenP; iP++) {
|
||
|
var newPath = [];
|
||
|
newPath.push(['M', ePath[iP][1], ePath[iP][2]]);
|
||
|
newPath.push(['L', ePath[iP + 1][1], ePath[iP + 1][2]]);
|
||
|
|
||
|
var line1_from_x = newPath[0][1];
|
||
|
var line1_from_y = newPath[0][2];
|
||
|
var line1_to_x = newPath[1][1];
|
||
|
var line1_to_y = newPath[1][2];
|
||
|
|
||
|
for (var lP = 0, lenlP = lPath.length - 1; lP < lenlP; lP++) {
|
||
|
var newLinePath = [];
|
||
|
newLinePath.push(['M', lPath[lP][1], lPath[lP][2]]);
|
||
|
newLinePath.push(['L', lPath[lP + 1][1], lPath[lP + 1][2]]);
|
||
|
|
||
|
var line2_from_x = newLinePath[0][1];
|
||
|
var line2_from_y = newLinePath[0][2];
|
||
|
var line2_to_x = newLinePath[1][1];
|
||
|
var line2_to_y = newLinePath[1][2];
|
||
|
|
||
|
var res = checkLineIntersection(line1_from_x, line1_from_y, line1_to_x, line1_to_y, line2_from_x, line2_from_y, line2_to_x, line2_to_y);
|
||
|
if (res.onLine1 && res.onLine2) {
|
||
|
|
||
|
var newSegment;
|
||
|
if (line2_from_y === line2_to_y) {
|
||
|
if (line2_from_x > line2_to_x) {
|
||
|
newSegment = ['L', res.x + lineWith * 2, line2_from_y];
|
||
|
lPath.splice(lP + 1, 0, newSegment);
|
||
|
newSegment = ['C', res.x + lineWith * 2, line2_from_y, res.x, line2_from_y - lineWith * 4, res.x - lineWith * 2, line2_from_y];
|
||
|
lPath.splice(lP + 2, 0, newSegment);
|
||
|
line.attr('path', lPath);
|
||
|
} else {
|
||
|
newSegment = ['L', res.x - lineWith * 2, line2_from_y];
|
||
|
lPath.splice(lP + 1, 0, newSegment);
|
||
|
newSegment = ['C', res.x - lineWith * 2, line2_from_y, res.x, line2_from_y - lineWith * 4, res.x + lineWith * 2, line2_from_y];
|
||
|
lPath.splice(lP + 2, 0, newSegment);
|
||
|
line.attr('path', lPath);
|
||
|
}
|
||
|
} else {
|
||
|
if (line2_from_y > line2_to_y) {
|
||
|
newSegment = ['L', line2_from_x, res.y + lineWith * 2];
|
||
|
lPath.splice(lP + 1, 0, newSegment);
|
||
|
newSegment = ['C', line2_from_x, res.y + lineWith * 2, line2_from_x + lineWith * 4, res.y, line2_from_x, res.y - lineWith * 2];
|
||
|
lPath.splice(lP + 2, 0, newSegment);
|
||
|
line.attr('path', lPath);
|
||
|
} else {
|
||
|
newSegment = ['L', line2_from_x, res.y - lineWith * 2];
|
||
|
lPath.splice(lP + 1, 0, newSegment);
|
||
|
newSegment = ['C', line2_from_x, res.y - lineWith * 2, line2_from_x + lineWith * 4, res.y, line2_from_x, res.y + lineWith * 2];
|
||
|
lPath.splice(lP + 2, 0, newSegment);
|
||
|
line.attr('path', lPath);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
lP += 2;
|
||
|
len += 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.chart.lines.push(line);
|
||
|
}
|
||
|
|
||
|
if (!this.chart.maxXFromLine || (this.chart.maxXFromLine && maxX > this.chart.maxXFromLine)) {
|
||
|
this.chart.maxXFromLine = maxX;
|
||
|
}
|
||
|
};
|
||
|
function Start(chart, options) {
|
||
|
var symbol = chart.paper.rect(0, 0, 0, 0, 20);
|
||
|
options = options || {};
|
||
|
options.text = options.text || 'Start';
|
||
|
Symbol.call(this, chart, options, symbol);
|
||
|
}
|
||
|
f.inherits(Start, Symbol);
|
||
|
|
||
|
|
||
|
// Start.prototype.render = function() {
|
||
|
// if (this.next) {
|
||
|
// var lineLength = this.chart.options.symbols[this.symbolType]['line-length'] || this.chart.options['line-length'];
|
||
|
|
||
|
// var bottomPoint = this.getBottom();
|
||
|
// var topPoint = this.next.getTop();
|
||
|
|
||
|
// if (!this.next.isPositioned) {
|
||
|
// this.next.shiftY(this.getY() + this.height + lineLength);
|
||
|
// this.next.setX(bottomPoint.x - this.next.width/2);
|
||
|
// this.next.isPositioned = true;
|
||
|
|
||
|
// this.next.render();
|
||
|
// }
|
||
|
// }
|
||
|
// };
|
||
|
|
||
|
// Start.prototype.renderLines = function() {
|
||
|
// if (this.next) {
|
||
|
// this.drawLineTo(this.next);
|
||
|
// }
|
||
|
// };
|
||
|
function End(chart, options) {
|
||
|
var symbol = chart.paper.rect(0, 0, 0, 0, 20);
|
||
|
options = options || {};
|
||
|
options.text = options.text || 'End';
|
||
|
Symbol.call(this, chart, options, symbol);
|
||
|
}
|
||
|
f.inherits(End, Symbol);
|
||
|
function Operation(chart, options) {
|
||
|
var symbol = chart.paper.rect(0, 0, 0, 0);
|
||
|
options = options || {};
|
||
|
Symbol.call(this, chart, options, symbol);
|
||
|
}
|
||
|
f.inherits(Operation, Symbol);
|
||
|
function Subroutine(chart, options) {
|
||
|
var symbol = chart.paper.rect(0, 0, 0, 0);
|
||
|
options = options || {};
|
||
|
Symbol.call(this, chart, options, symbol);
|
||
|
|
||
|
symbol.attr({
|
||
|
width: this.text.getBBox().width + 4 * this.getAttr('text-margin')
|
||
|
});
|
||
|
|
||
|
this.text.attr({
|
||
|
'x': 2 * this.getAttr('text-margin')
|
||
|
});
|
||
|
|
||
|
var innerWrap = chart.paper.rect(0, 0, 0, 0);
|
||
|
innerWrap.attr({
|
||
|
x: this.getAttr('text-margin'),
|
||
|
stroke: this.getAttr('element-color'),
|
||
|
'stroke-width': this.getAttr('line-width'),
|
||
|
width: this.text.getBBox().width + 2 * this.getAttr('text-margin'),
|
||
|
height: this.text.getBBox().height + 2 * this.getAttr('text-margin'),
|
||
|
fill: this.getAttr('fill')
|
||
|
});
|
||
|
if (options.key) { innerWrap.node.id = options.key + 'i'; }
|
||
|
|
||
|
var font = this.getAttr('font');
|
||
|
var fontF = this.getAttr('font-family');
|
||
|
var fontW = this.getAttr('font-weight');
|
||
|
|
||
|
if (font) innerWrap.attr({ 'font': font });
|
||
|
if (fontF) innerWrap.attr({ 'font-family': fontF });
|
||
|
if (fontW) innerWrap.attr({ 'font-weight': fontW });
|
||
|
|
||
|
if (options.link) { innerWrap.attr('href', options.link); }
|
||
|
if (options.target) { innerWrap.attr('target', options.target); }
|
||
|
this.group.push(innerWrap);
|
||
|
innerWrap.insertBefore(this.text);
|
||
|
|
||
|
this.initialize();
|
||
|
}
|
||
|
f.inherits(Subroutine, Symbol);
|
||
|
function InputOutput(chart, options) {
|
||
|
options = options || {};
|
||
|
Symbol.call(this, chart, options);
|
||
|
this.textMargin = this.getAttr('text-margin');
|
||
|
|
||
|
this.text.attr({
|
||
|
x: this.textMargin * 3
|
||
|
});
|
||
|
|
||
|
var width = this.text.getBBox().width + 4 * this.textMargin;
|
||
|
var height = this.text.getBBox().height + 2 * this.textMargin;
|
||
|
var startX = this.textMargin;
|
||
|
var startY = height/2;
|
||
|
|
||
|
var start = {x: startX, y: startY};
|
||
|
var points = [
|
||
|
{x: startX - this.textMargin, y: height},
|
||
|
{x: startX - this.textMargin + width, y: height},
|
||
|
{x: startX - this.textMargin + width + 2 * this.textMargin, y: 0},
|
||
|
{x: startX - this.textMargin + 2 * this.textMargin, y: 0},
|
||
|
{x: startX, y: startY}
|
||
|
];
|
||
|
|
||
|
var symbol = drawPath(chart, start, points);
|
||
|
|
||
|
symbol.attr({
|
||
|
stroke: this.getAttr('element-color'),
|
||
|
'stroke-width': this.getAttr('line-width'),
|
||
|
fill: this.getAttr('fill')
|
||
|
});
|
||
|
if (options.link) { symbol.attr('href', options.link); }
|
||
|
if (options.target) { symbol.attr('target', options.target); }
|
||
|
if (options.key) { symbol.node.id = options.key; }
|
||
|
symbol.node.setAttribute('class', this.getAttr('class'));
|
||
|
|
||
|
this.text.attr({
|
||
|
y: symbol.getBBox().height/2
|
||
|
});
|
||
|
|
||
|
this.group.push(symbol);
|
||
|
symbol.insertBefore(this.text);
|
||
|
|
||
|
this.initialize();
|
||
|
}
|
||
|
f.inherits(InputOutput, Symbol);
|
||
|
|
||
|
InputOutput.prototype.getLeft = function() {
|
||
|
var y = this.getY() + this.group.getBBox().height/2;
|
||
|
var x = this.getX() + this.textMargin;
|
||
|
return {x: x, y: y};
|
||
|
};
|
||
|
|
||
|
InputOutput.prototype.getRight = function() {
|
||
|
var y = this.getY() + this.group.getBBox().height/2;
|
||
|
var x = this.getX() + this.group.getBBox().width - this.textMargin;
|
||
|
return {x: x, y: y};
|
||
|
};
|
||
|
function Condition(chart, options) {
|
||
|
options = options || {};
|
||
|
Symbol.call(this, chart, options);
|
||
|
this.textMargin = this.getAttr('text-margin');
|
||
|
this.yes_direction = 'bottom';
|
||
|
this.no_direction = 'right';
|
||
|
if (options.yes && options['direction_yes'] && options.no && !options['direction_no']) {
|
||
|
if (options['direction_yes'] === 'right') {
|
||
|
this.no_direction = 'bottom';
|
||
|
this.yes_direction = 'right';
|
||
|
} else {
|
||
|
this.no_direction = 'right';
|
||
|
this.yes_direction = 'bottom';
|
||
|
}
|
||
|
} else if (options.yes && !options['direction_yes'] && options.no && options['direction_no']) {
|
||
|
if (options['direction_no'] === 'right') {
|
||
|
this.yes_direction = 'bottom';
|
||
|
this.no_direction = 'right';
|
||
|
} else {
|
||
|
this.yes_direction = 'right';
|
||
|
this.no_direction = 'bottom';
|
||
|
}
|
||
|
} else {
|
||
|
this.yes_direction = 'bottom';
|
||
|
this.no_direction = 'right';
|
||
|
}
|
||
|
|
||
|
this.yes_direction = this.yes_direction || 'bottom';
|
||
|
this.no_direction = this.no_direction || 'right';
|
||
|
|
||
|
this.text.attr({
|
||
|
x: this.textMargin * 2
|
||
|
});
|
||
|
|
||
|
var width = this.text.getBBox().width + 3 * this.textMargin;
|
||
|
width += width/2;
|
||
|
var height = this.text.getBBox().height + 2 * this.textMargin;
|
||
|
height += height/2;
|
||
|
height = Math.max(width * 0.5, height);
|
||
|
var startX = width/4;
|
||
|
var startY = height/4;
|
||
|
|
||
|
this.text.attr({
|
||
|
x: startX + this.textMargin/2
|
||
|
});
|
||
|
|
||
|
var start = {x: startX, y: startY};
|
||
|
var points = [
|
||
|
{x: startX - width/4, y: startY + height/4},
|
||
|
{x: startX - width/4 + width/2, y: startY + height/4 + height/2},
|
||
|
{x: startX - width/4 + width, y: startY + height/4},
|
||
|
{x: startX - width/4 + width/2, y: startY + height/4 - height/2},
|
||
|
{x: startX - width/4, y: startY + height/4}
|
||
|
];
|
||
|
|
||
|
var symbol = drawPath(chart, start, points);
|
||
|
|
||
|
symbol.attr({
|
||
|
stroke: this.getAttr('element-color'),
|
||
|
'stroke-width': this.getAttr('line-width'),
|
||
|
fill: this.getAttr('fill')
|
||
|
});
|
||
|
if (options.link) { symbol.attr('href', options.link); }
|
||
|
if (options.target) { symbol.attr('target', options.target); }
|
||
|
if (options.key) { symbol.node.id = options.key; }
|
||
|
symbol.node.setAttribute('class', this.getAttr('class'));
|
||
|
|
||
|
this.text.attr({
|
||
|
y: symbol.getBBox().height/2
|
||
|
});
|
||
|
|
||
|
this.group.push(symbol);
|
||
|
symbol.insertBefore(this.text);
|
||
|
|
||
|
this.initialize();
|
||
|
}
|
||
|
f.inherits(Condition, Symbol);
|
||
|
|
||
|
Condition.prototype.render = function() {
|
||
|
|
||
|
if (this.yes_direction) {
|
||
|
this[this.yes_direction + '_symbol'] = this.yes_symbol;
|
||
|
}
|
||
|
|
||
|
if (this.no_direction) {
|
||
|
this[this.no_direction + '_symbol'] = this.no_symbol;
|
||
|
}
|
||
|
|
||
|
var lineLength = this.getAttr('line-length');
|
||
|
|
||
|
if (this.bottom_symbol) {
|
||
|
var bottomPoint = this.getBottom();
|
||
|
var topPoint = this.bottom_symbol.getTop();
|
||
|
|
||
|
if (!this.bottom_symbol.isPositioned) {
|
||
|
this.bottom_symbol.shiftY(this.getY() + this.height + lineLength);
|
||
|
this.bottom_symbol.setX(bottomPoint.x - this.bottom_symbol.width/2);
|
||
|
this.bottom_symbol.isPositioned = true;
|
||
|
|
||
|
this.bottom_symbol.render();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.right_symbol) {
|
||
|
var rightPoint = this.getRight();
|
||
|
var leftPoint = this.right_symbol.getLeft();
|
||
|
|
||
|
if (!this.right_symbol.isPositioned) {
|
||
|
|
||
|
this.right_symbol.setY(rightPoint.y - this.right_symbol.height/2);
|
||
|
this.right_symbol.shiftX(this.group.getBBox().x + this.width + lineLength);
|
||
|
|
||
|
var self = this;
|
||
|
(function shift() {
|
||
|
var hasSymbolUnder = false;
|
||
|
var symb;
|
||
|
for (var i = 0, len = self.chart.symbols.length; i < len; i++) {
|
||
|
symb = self.chart.symbols[i];
|
||
|
|
||
|
var diff = Math.abs(symb.getCenter().x - self.right_symbol.getCenter().x);
|
||
|
if (symb.getCenter().y > self.right_symbol.getCenter().y && diff <= self.right_symbol.width/2) {
|
||
|
hasSymbolUnder = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hasSymbolUnder) {
|
||
|
self.right_symbol.setX(symb.getX() + symb.width + lineLength);
|
||
|
shift();
|
||
|
}
|
||
|
})();
|
||
|
|
||
|
this.right_symbol.isPositioned = true;
|
||
|
|
||
|
this.right_symbol.render();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Condition.prototype.renderLines = function() {
|
||
|
if (this.yes_symbol) {
|
||
|
this.drawLineTo(this.yes_symbol, this.getAttr('yes-text'), this.yes_direction);
|
||
|
}
|
||
|
|
||
|
if (this.no_symbol) {
|
||
|
this.drawLineTo(this.no_symbol, this.getAttr('no-text'), this.no_direction);
|
||
|
}
|
||
|
};
|
||
|
function parse(input) {
|
||
|
input = input || '';
|
||
|
input = input.trim();
|
||
|
|
||
|
var chart = {
|
||
|
symbols: {},
|
||
|
start: null,
|
||
|
drawSVG: function(container, options) {
|
||
|
var self = this;
|
||
|
|
||
|
if (this.diagram) {
|
||
|
this.diagram.clean();
|
||
|
}
|
||
|
|
||
|
var diagram = new FlowChart(container, options);
|
||
|
this.diagram = diagram;
|
||
|
var dispSymbols = {};
|
||
|
|
||
|
function getDisplaySymbol(s) {
|
||
|
if (dispSymbols[s.key]) {
|
||
|
return dispSymbols[s.key];
|
||
|
}
|
||
|
|
||
|
switch (s.symbolType) {
|
||
|
case 'start':
|
||
|
dispSymbols[s.key] = new Start(diagram, s);
|
||
|
break;
|
||
|
case 'end':
|
||
|
dispSymbols[s.key] = new End(diagram, s);
|
||
|
break;
|
||
|
case 'operation':
|
||
|
dispSymbols[s.key] = new Operation(diagram, s);
|
||
|
break;
|
||
|
case 'inputoutput':
|
||
|
dispSymbols[s.key] = new InputOutput(diagram, s);
|
||
|
break;
|
||
|
case 'subroutine':
|
||
|
dispSymbols[s.key] = new Subroutine(diagram, s);
|
||
|
break;
|
||
|
case 'condition':
|
||
|
dispSymbols[s.key] = new Condition(diagram, s);
|
||
|
break;
|
||
|
default:
|
||
|
return new Error('Wrong symbol type!');
|
||
|
}
|
||
|
|
||
|
return dispSymbols[s.key];
|
||
|
}
|
||
|
|
||
|
(function constructChart(s, prevDisp, prev) {
|
||
|
var dispSymb = getDisplaySymbol(s);
|
||
|
|
||
|
if (self.start === s) {
|
||
|
diagram.startWith(dispSymb);
|
||
|
} else if (prevDisp && prev && !prevDisp.pathOk) {
|
||
|
if (prevDisp instanceof(Condition)) {
|
||
|
if (prev.yes === s) {
|
||
|
prevDisp.yes(dispSymb);
|
||
|
}
|
||
|
if (prev.no === s) {
|
||
|
prevDisp.no(dispSymb);
|
||
|
}
|
||
|
} else {
|
||
|
prevDisp.then(dispSymb);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dispSymb.pathOk) {
|
||
|
return dispSymb;
|
||
|
}
|
||
|
|
||
|
if (dispSymb instanceof(Condition)) {
|
||
|
if (s.yes) {
|
||
|
constructChart(s.yes, dispSymb, s);
|
||
|
}
|
||
|
if (s.no) {
|
||
|
constructChart(s.no, dispSymb, s);
|
||
|
}
|
||
|
} else if (s.next) {
|
||
|
constructChart(s.next, dispSymb, s);
|
||
|
}
|
||
|
|
||
|
return dispSymb;
|
||
|
})(this.start);
|
||
|
|
||
|
diagram.render();
|
||
|
},
|
||
|
clean: function() {
|
||
|
this.diagram.clean();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var lines = [];
|
||
|
var prevBreak = 0;
|
||
|
for (var i0 = 1, i0len = input.length; i0 < i0len; i0++) {
|
||
|
if(input[i0] === '\n' && input[i0 - 1] !== '\\') {
|
||
|
var line0 = input.substring(prevBreak, i0);
|
||
|
prevBreak = i0 + 1;
|
||
|
lines.push(line0.replace(/\\\n/g, '\n'));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(prevBreak < input.length) {
|
||
|
lines.push(input.substr(prevBreak));
|
||
|
}
|
||
|
|
||
|
for (var l = 1, len = lines.length; l < len;) {
|
||
|
var currentLine = lines[l];
|
||
|
|
||
|
if (currentLine.indexOf(': ') < 0 && currentLine.indexOf('(') < 0 && currentLine.indexOf(')') < 0 && currentLine.indexOf('->') < 0 && currentLine.indexOf('=>') < 0) {
|
||
|
lines[l - 1] += '\n' + currentLine;
|
||
|
lines.splice(l, 1);
|
||
|
len--;
|
||
|
} else {
|
||
|
l++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function getSymbol(s) {
|
||
|
var startIndex = s.indexOf('(') + 1;
|
||
|
var endIndex = s.indexOf(')');
|
||
|
if (startIndex >= 0 && endIndex >= 0) {
|
||
|
return chart.symbols[s.substring(0, startIndex - 1)];
|
||
|
}
|
||
|
return chart.symbols[s];
|
||
|
}
|
||
|
|
||
|
function getNextPath(s) {
|
||
|
var next = 'next';
|
||
|
var startIndex = s.indexOf('(') + 1;
|
||
|
var endIndex = s.indexOf(')');
|
||
|
if (startIndex >= 0 && endIndex >= 0) {
|
||
|
next = flowSymb.substring(startIndex, endIndex);
|
||
|
if (next.indexOf(',') < 0) {
|
||
|
if (next !== 'yes' && next !== 'no') {
|
||
|
next = 'next, ' + next;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return next;
|
||
|
}
|
||
|
|
||
|
while (lines.length > 0) {
|
||
|
var line = lines.splice(0, 1)[0];
|
||
|
|
||
|
if (line.indexOf('=>') >= 0) {
|
||
|
// definition
|
||
|
var parts = line.split('=>');
|
||
|
var symbol = {
|
||
|
key: parts[0],
|
||
|
symbolType: parts[1],
|
||
|
text: null,
|
||
|
link: null,
|
||
|
target: null,
|
||
|
flowstate: null
|
||
|
};
|
||
|
|
||
|
var sub;
|
||
|
|
||
|
if (symbol.symbolType.indexOf(': ') >= 0) {
|
||
|
sub = symbol.symbolType.split(': ');
|
||
|
symbol.symbolType = sub[0];
|
||
|
symbol.text = sub[1];
|
||
|
}
|
||
|
|
||
|
if (symbol.text && symbol.text.indexOf(':>') >= 0) {
|
||
|
sub = symbol.text.split(':>');
|
||
|
symbol.text = sub[0];
|
||
|
symbol.link = sub[1];
|
||
|
} else if (symbol.symbolType.indexOf(':>') >= 0) {
|
||
|
sub = symbol.symbolType.split(':>');
|
||
|
symbol.symbolType = sub[0];
|
||
|
symbol.link = sub[1];
|
||
|
}
|
||
|
|
||
|
if (symbol.symbolType.indexOf('\n') >= 0) {
|
||
|
symbol.symbolType = symbol.symbolType.split('\n')[0];
|
||
|
}
|
||
|
|
||
|
/* adding support for links */
|
||
|
if (symbol.link) {
|
||
|
var startIndex = symbol.link.indexOf('[') + 1;
|
||
|
var endIndex = symbol.link.indexOf(']');
|
||
|
if (startIndex >= 0 && endIndex >= 0) {
|
||
|
symbol.target = symbol.link.substring(startIndex, endIndex);
|
||
|
symbol.link = symbol.link.substring(0, startIndex - 1);
|
||
|
}
|
||
|
}
|
||
|
/* end of link support */
|
||
|
|
||
|
/* adding support for flowstates */
|
||
|
if (symbol.text) {
|
||
|
if (symbol.text.indexOf('|') >= 0) {
|
||
|
var txtAndState = symbol.text.split('|');
|
||
|
symbol.text = txtAndState[0];
|
||
|
symbol.flowstate = txtAndState[1].trim();
|
||
|
}
|
||
|
}
|
||
|
/* end of flowstate support */
|
||
|
|
||
|
chart.symbols[symbol.key] = symbol;
|
||
|
|
||
|
} else if (line.indexOf('->') >= 0) {
|
||
|
// flow
|
||
|
var flowSymbols = line.split('->');
|
||
|
for (var i = 0, lenS = flowSymbols.length; i < lenS; i++) {
|
||
|
var flowSymb = flowSymbols[i];
|
||
|
|
||
|
var realSymb = getSymbol(flowSymb);
|
||
|
var next = getNextPath(flowSymb);
|
||
|
|
||
|
var direction = null;
|
||
|
if (next.indexOf(',') >= 0) {
|
||
|
var condOpt = next.split(',');
|
||
|
next = condOpt[0];
|
||
|
direction = condOpt[1].trim();
|
||
|
}
|
||
|
|
||
|
if (!chart.start) {
|
||
|
chart.start = realSymb;
|
||
|
}
|
||
|
|
||
|
if (i + 1 < lenS) {
|
||
|
var nextSymb = flowSymbols[i + 1];
|
||
|
realSymb[next] = getSymbol(nextSymb);
|
||
|
realSymb['direction_' + next] = direction;
|
||
|
direction = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
return chart;
|
||
|
}
|
||
|
// public api interface
|
||
|
flowchart.parse = parse;
|
||
|
|
||
|
})();
|