/**
* This file is part of FlowgorithmJS.
* Copyright (C) 2018-2024 Andrea Vallorani
* FlowgorithmJS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* FlowgorithmJS
* @version 0.17a1
* @date 2020-05-02
* @author Andrea Vallorani
*/
var maxW,flowX=0,funX=0;
var flow;
function drawFlowchartFromUrl(url,selector,options){
$.ajax({
type: "GET",
url: url,
dataType: "xml",
cache: false,
success: function(result) {
drawFlowchart($(result),selector,options);
}
});
}
function drawFlowchartFromSource(source,selector,options){
drawFlowchart($($.parseXML(source)),selector,options);
}
function drawFlowchart($xml,selector,options){
//default settings
options = $.extend(true,{
aH: 12, //arrow length
aT: 7, //arrow tip size
groupInput: false,
viewDesc: true, //print description
itMode: 2, //pre-Iteraction style
labelTrue: "True",
labelFalse: "False"
},options);
//--
var $output = $(selector);
$output.trigger("flowgorithmjs:preload");
flowX=0,funX=0;
flow = {Y:0,fWidth:0,dim:[]};
$output.empty();
if(options.viewDesc){
var title = $xml.find('attribute[name=name]').attr('value');
var desc = $xml.find('attribute[name=about]').attr('value');
$output.append(''+desc+'
'+
''+
'
'+title+' '+
'
'+$xml.find('attribute[name=authors]').attr('value')+' '+
'
'+$xml.find('attribute[name=saved]').attr('value')+' '+
'
Download SVG '+
'
');
}
$output.append(''+
' '+
' '+
' '+
' '+
' '+
' ').css('text-align','center');
//draw flowchart
var svg = '';
$xml.find('function').each(function(){
svg += drawFunction($(this));
});
$svg = $output.children('svg');
$svg.get(0).innerHTML += ''+svg+' ';
$svg.children('g').eq(0).remove();
var gwidth = $svg.children('g').get(0).getBBox().width;
var gheight = $svg.children('g').get(0).getBBox().height;
$svg.attr('width',gwidth+10).attr('height',gheight+15).attr('viewBox','0 0 '+(gwidth+10)+' '+(gheight+15));
var disc = 0-$svg.children('g').get(0).getBBox().x+5;
$svg.children('g').attr('transform',"translate("+(disc)+",0)");
$output.trigger("flowgorithmjs:postload");
function drawFunction($xml){
maxW = 0;
flow.Y = 0;
flow.fWidth = 0;
var name = $xml.attr('name');
var txtStart,txtEnd;
if(name==='Main'){
txtStart = 'START';
txtEnd = 'END';
}
else{
txtStart = name;
var params = $xml.find('parameter');
if(params.length>0){
txtStart += '(';
params.each(function(i){
if(i>0) txtStart += ',';
txtStart += $(this).attr('type')+' '+$(this).attr('name');
});
txtStart += ')';
}
txtEnd = 'RETURN '+$xml.attr('variable');
}
var s = drawStart(txtStart);
s += drawSequence($xml.children('body'));
s += drawEnd(txtEnd);
var fwidth = calcBlockWidth(s)+5;
var fx = calcBlockX(s);
var tempX = funX;
if(tempX!=0){
tempX+=fx;
funX+=fwidth;
}
else{
funX=fwidth-fx;
}
flow.dim.push(flow.Y+20);
return ''+s+' ';
};
function drawSequence($xml){
var s = '';
var oldY = flow.Y;
flow.Y = 0;
var nextI = -1;
$xml.children().each(function(i){
var $el = $(this);
switch($el.prop("tagName")){
case 'declare': var array = ($el.attr('array')==="True") ? '['+$el.attr('size')+']' : '';
s += drawDeclare($el.attr('type')+' '+$el.attr('name')+array);
break;
case 'output': var outString = $el.attr('expression');
if($el.attr('newline')==='False') outString+=' ...';
s += drawOutput(outString); break;
case 'input': if(i>nextI){
var vars = $el.attr('variable');
if(options.groupInput){
nextI = i+1;
while($xml.children().eq(nextI).prop("tagName")=='input'){
vars += ', '+$xml.children().eq(nextI).attr('variable');
nextI++;
}
}
s += drawInput(vars);
} break;
case 'assign': s += drawAssign($el.attr('variable')+'='+$el.attr('expression')); break;
case 'call': s += drawCall($el.attr('expression')); break;
case 'if': s += drawSelection($el.attr('expression'),$el.children('then'),$el.children('else')); break;
case 'for': s += drawPreIteraction($el.attr('variable')+'='+$el.attr('start')+' TO '+$el.attr('end'),$el); break;
case 'while': s += drawPreIteraction($el.attr('expression'),$el); break;
case 'do': s += drawPostIteraction($el.attr('expression'),$el); break;
case 'more': s += drawMore(); break;
}
});
s += ' ';
flow.Y += oldY;
//console.log("sequence ("+flow.Y+")");
//if(flow.Y>maxH) maxH = flow.Y;
//if($xml.children('if').length>0) maxW+=100;
return s;
};
function drawStart(content){
flow.Y += 20;
var padding = 10;
var l = Math.max(calcExtraWidth(content),50)/2+padding;
var s = ''+
' '+
' '+
' '+content+' '+
' '+
' ';
flow.Y += 20;
return s;
};
function drawEnd(content){
var padding = 10;
var l = Math.max(calcExtraWidth(content),50)/2+padding;
var s = ''+
drawArrow()+
' '+
' '+
' '+content+' '+
' '+
' ';
flow.Y += 40+options.aH+options.aT;
return s;
};
function drawDeclare(content){
//console.log("declare ("+flow.Y+")");
var l = Math.max(calcExtraWidth(content),60)+20;
var s = ''+
drawArrow()+
' '+
' '+
' '+
' '+
' '+content+' '+
' '+
' ';
flow.Y += 36+options.aH+options.aT;
return s;
};
function drawAssign(content){
//console.log("assign ("+flow.Y+")");
var l = Math.max(calcExtraWidth(content),60)+10;
var s = ''+
drawArrow()+
' '+
' '+
' '+content+' '+
' '+
' ';
flow.Y += 36+options.aH+options.aT;
return s;
};
function drawInput(content){
//console.log("input ("+flow.Y+")");
var l = Math.max(calcExtraWidth(content),50);
var s = ''+
drawArrow()+
' '+
' '+
' '+
' '+content+' '+
' i '+
' '+
' ';
flow.Y += 36+options.aH+options.aT;
return s;
};
function drawOutput(content){
//console.log("output ("+flow.Y+")");
var l = Math.max(calcExtraWidth(content),50);
var s = ''+
drawArrow()+
' '+
' '+
' '+
' '+content+' '+
' o '+
' '+
' ';
flow.Y += 36+options.aH+options.aT;
return s;
};
function drawCall(content){
var l = Math.max(calcExtraWidth(content),60)+20;
var s = ''+
drawArrow()+
' '+
' '+
' '+
' '+
' '+content+' '+
' '+
' ';
flow.Y += 36+options.aH+options.aT;
return s;
};
function drawMore(){
var text = "...";
var l = calcExtraWidth(text);
var s = ''+
drawArrow()+
' '+
' '+text+' '+
' '+
' ';
flow.Y += 28+options.aH+options.aT;
return s;
};
function drawEmptyBlock(){
var s = ''+
' '+
' ';
flow.Y += options.aH*3;
return s;
};
function splitCondition(condition){
var i,tot=0,start=0,nl=false;
var lines = [];
for(i=0;i5){
lines[tot]=condition.substring(start,i);
tot++;
start=i+1;
nl=false;
}
else nl=true;
}
}
lines[tot]=condition.substring(start);
//console.log(lines);
return lines;
}
function drawSelection(condition,$branchTrue,$branchFalse){
var labelTrueSize = Math.max(calcExtraWidth(options.labelTrue),20);
var labelFalseSize = Math.max(calcExtraWidth(options.labelFalse),20);
var minPathWidth = Math.max(30,labelTrueSize+5,labelFalseSize+5); //minimum path width
console.log(labelFalseSize*2);
var circleRadius = 4; //circle radius of rejoining paths
var parts = splitCondition(condition);
var romH;
var contidionWidth = 0;
if(parts.length>1){
condition = '';
var nRows = parts.length;
$(parts).each(function(i){
contidionWidth = Math.max(calcExtraWidth(this),contidionWidth);
condition += ''+this.escape()+' ';
});
contidionWidth += 20*nRows;
var yc = 11*nRows;
condition = ''+condition+' ';
romH = 20*nRows;
}
else{
contidionWidth = calcExtraWidth(condition);
condition = ''+condition.escape()+' ';
romH = 27;
}
contidionWidth = Math.max(contidionWidth,50)+30;
var oldY = 0;
oldY = flow.Y;
flow.Y = 0;
var halfCondition = contidionWidth/2;
var svgSequenceTrue = '';
var trueWidth = halfCondition-5;
if($branchTrue.children().length>0){
svgSequenceTrue = drawSequence($branchTrue);
trueWidth+=Math.max(calcBlockX(svgSequenceTrue),minPathWidth);
}
else trueWidth+=minPathWidth;
var s = ''+
drawArrow()+
' '+
' '+
' '+
condition+
' '+
' '+
' '+
' '+options.labelTrue+' '+
' '+
' '+svgSequenceTrue+' ';
var branchTrueHeight = flow.Y+romH;
flow.Y = 0;
var svgSequenceFalse = '';
var falseWidth = halfCondition-5;
if($branchFalse.children().length>0){
svgSequenceFalse = drawSequence($branchFalse);
falseWidth += Math.max(calcBlockWidth(svgSequenceFalse)-calcBlockX(svgSequenceFalse),minPathWidth);
}
else falseWidth+=minPathWidth;
s+= ' '+
' '+
' '+options.labelFalse+' '+
' '+
' '+svgSequenceFalse+' ';
var branchFalseHeight = flow.Y+romH;
if(branchTrueHeight>branchFalseHeight){
s += ' ';
}
else if(branchTrueHeight ';
}
var endHeight=options.aT+options.aH+3;
var maxBranchesHeight = Math.max(branchTrueHeight,branchFalseHeight)+10;
s += ' '+
' '+
' '+
' '+
' '+
' '+
' ';
flow.Y = oldY+maxBranchesHeight+circleRadius+endHeight+options.aT+options.aH;
calcBlockWidth(s);
return s;
};
function drawPreIteraction(condition,$content){
var labelTrueSize = Math.max(calcExtraWidth(options.labelTrue),16);
var labelFalseSize = Math.max(calcExtraWidth(options.labelFalse),16);
var parts = splitCondition(condition);
var romH;
var contidionWidth=0;
if(parts.length>1){
condition = '';
$(parts).each(function(i){
contidionWidth = Math.max(calcExtraWidth(this),contidionWidth);
condition += ''+this.escape()+' ';
});
contidionWidth += 2*parts.length;
var yc = parts.length;
condition = ''+condition+' ';
romH = 9*parts.length;
}
else{
contidionWidth = Math.max(calcExtraWidth(condition),60);
condition = ''+condition.escape()+' ';
romH = 20;
}
var condW2 = contidionWidth/2;
var s = ''+drawArrow();
var oldY = flow.Y;
flow.Y = 0;
var contentDraw = ($content.children().length>0) ? drawSequence($content) : drawEmptyBlock();
var contentWidth = calcBlockWidth(contentDraw);
var half = Math.max(calcBlockX(contentDraw),40);
s += ' '+
' '+
' '+
condition+
' ';
//don't use this variant
if(options.itMode===1){
s += ' '+
' '+
' '+options.labelTrue+' '+
' ';
s += contentDraw;
s += ' '+
' '+
' '+
' '+
' '+
' '+
' '+
' '+options.labelFalse+' '+
' '+
' '+
' ';
flow.Y += oldY+options.aH+options.aT+20;
}
else if(options.itMode===2){
var down=Math.max(0,romH-options.aH-options.aT);
s += ' '+
' '+
' '+options.labelTrue+' '+
' ';
s += contentDraw;
s += ' '+
' '+
' '+
' '+
' '+
' '+
' '+
' '+options.labelFalse+' '+
' '+
' '+
'';
flow.Y += oldY+(romH*2)+options.aH+options.aT-15;
}
else if(options.itMode===3){
s += ' '+
' '+options.labelTrue+' '+
' ';
s += contentDraw;
s += ' '+
' '+
' '+
' '+
' '+
' '+
' '+
' '+options.labelFalse+' '+
' '+
' '+
'';
flow.Y += oldY+(romH*2)+20+options.aH+options.aT;
}
return s;
};
function drawPostIteraction(condition,$content){
var parts = splitCondition(condition);
var romH;
var contidionWidth = 0;
if(parts.length>1){
condition = '';
$(parts).each(function(i){
contidionWidth = Math.max(calcExtraWidth(this),contidionWidth);
condition += ''+this.escape()+' ';
});
contidionWidth+=2*parts.length;
var yc = parts.length;
condition = ''+condition+' ';
romH = 9*parts.length;
}
else{
contidionWidth = calcExtraWidth(condition);
condition = ''+condition.escape()+' ';
romH = 20;
}
contidionWidth = Math.max(contidionWidth,60);
var condW2 = contidionWidth/2;
var s = ' ';
var oldY = flow.Y;
flow.Y = 0;
var contentDraw = ($content.children().length>0) ? drawSequence($content) : drawEmptyBlock();
var contentWidth = calcBlockWidth(contentDraw);
var half = Math.max(calcBlockX(contentDraw),condW2+40);
half+=6;
flow.Y += 15;
s += ' ';
if(options.itMode===1 || options.itMode===2 || options.itMode===3){
s += ' '+
' '+
' T '+
' ';
s += contentDraw+
' '+
' '+
' ';
s += ' '+
' '+
' '+
' '+
condition+
' F '+
' '+
' ';
s += ' '+
' '+
' ';
flow.Y += romH*2;
flow.Y += oldY+options.aH+options.aT+10;
}
return s;
};
function calcExtraWidth(content){
$('#ttest').text(content);
var l = $('#ttest').get(0).getBBox().width;
maxW = Math.max(maxW,l);
return l;
};
function calcBlockWidth(content){
$('#gtest').get(0).innerHTML = ''+content+' ';
var l = $('#gtest').children('g').get(0).getBBox().width;
maxW = Math.max(maxW,l);
return l;
};
function calcBlockX(content){
$("#gtest").get(0).innerHTML = ''+content+' ';
return Math.abs($('#gtest').children('g').get(0).getBBox().x);
};
function drawArrow(){
return ' ';
};
function getSVGContents(inputString){
var domParser = new DOMParser();
var svgDOM = domParser.parseFromString(inputString,'text/xml').getElementsByTagName('g')[0];
return svgDOM.innerHTML;
};
}
function downloadSVG(selector,strFileName){
var strData = $(selector+' svg').clone().find('#ttest').remove().end();
strData = strData[0].outerHTML;
var strMimeType = 'text/plain';
var D = document,
A = arguments,
a = D.createElement("a"),
d = A[0],
n = A[1],
t = A[2] || "text/plain";
//build download link:
a.href = "data:" + strMimeType + "charset=utf-8," + escape(strData);
if (window.MSBlobBuilder) { // IE10
var bb = new MSBlobBuilder();
bb.append(strData);
return navigator.msSaveBlob(bb, strFileName);
} /* end if(window.MSBlobBuilder) */
if ('download' in a) { //FF20, CH19
a.setAttribute("download", n);
a.innerHTML = "downloading...";
D.body.appendChild(a);
setTimeout(function() {
var e = D.createEvent("MouseEvents");
e.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(e);
D.body.removeChild(a);
}, 66);
return true;
}; /* end if('download' in a) */
//do iframe dataURL download: (older W3)
var f = D.createElement("iframe");
D.body.appendChild(f);
f.src = "data:" + (A[2] ? A[2] : "application/octet-stream") + (window.btoa ? ";base64" : "") + "," + (window.btoa ? window.btoa : escape)(strData);
setTimeout(function() {
D.body.removeChild(f);
}, 333);
return false;
};
String.prototype.escape = function() {
var tagsToReplace = {
'&': '&',
'<': '<',
'>': '>'
};
return this.replace(/[&<>]/g, function(tag) {
return tagsToReplace[tag] || tag;
});
};