/** * 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; }); };