/** * 数据流图绘制方法集 * 作者:刘广文 * 邮箱:liugw@imut.edu.cn * 版本:20230813 * 功能:基于canvas实现数据流图的绘制 */ var tool = 1; var current = -1; var flag=false; var mouseX= 0; var mouseY= 0; var mouseX1= 0; var mouseY1= 0; var aryPoint = []; var aryObject = []; //加载工具 function loadTools(target){ target.innerHTML = "
源点/终点
" + "
处理
" + "
" + "
" + "
数据存储
" + ""; } //鼠标抬起响应动作 function myMouseUp(e, page){ flag=false; //drawText(cxt, tool, mouseX, mouseY, mouseX1, mouseY1, text.value, color.value, getDataText(text.value, current)); pushTool(tool, mouseX, mouseY, mouseX1, mouseY1, text.value, color.value, bcolor.value, size.value, arrowtype.value); updateTools(cxt); } //鼠标移动响应动作 function myMouseMove(e, page){ mouseX1= e.pageX-page.offsetLeft; mouseY1= e.pageY-page.offsetTop; if(flag){ var aryPoint1 = []; var i = 0; while(i < aryPoint.length){ aryPoint1.push(aryPoint[i]); i ++; } updateTools(cxt); aryPoint = []; i = 0; while(i < aryPoint1.length){ aryPoint.push(aryPoint1[i]); i ++; } if (tool == 4){ var s = aryPoint[aryPoint.length - 1]; ary = s.split(","); var x = parseFloat(ary[0]), y = parseFloat(ary[1]); if (Math.abs(x - mouseX1) > 10 || Math.abs(y - mouseY1) > 10) aryPoint.push(mouseX1 + "," + mouseY1); } var arrowtype = document.getElementById("arrowtype"); var atype = arrowtype.value; drawTool(cxt, tool, mouseX, mouseY, mouseX1, mouseY1, color.value, bcolor.value, size.value, atype); } } //鼠标按下响应动作 function myMouseDown(e, page) { mouseX= e.pageX-page.offsetLeft; mouseY= e.pageY-page.offsetTop; aryPoint = []; aryPoint.push(mouseX + "," + mouseY); flag = true; } //保存 function save(){ var ary = []; var i = 0; while(i <= current){ ary.push(aryObject[i]); i ++; } aryObject = ary; updateButton(); const fileName = 'dataflow.txt'; var str = ''; i = 0; while(i < aryObject.length){ if (i == 0) str = aryObject[i] else{ str += "\r\n" str += aryObject[i] } i ++; } const content = str const blob = new Blob([content], {type: 'application/text'}); // 构造一个blob对象来处理数据 // 对于标签,只有 Firefox 和 Chrome(内核) 支持 download 属性 // IE10以上支持blob但是依然不支持download if ('download' in document.createElement('a')) { // 支持a标签download的浏览器 const link = document.createElement('a') // 创建a标签 link.download = fileName // a标签添加属性 link.style.display = 'none' link.href = URL.createObjectURL(blob) document.body.appendChild(link) link.click() // 执行下载 URL.revokeObjectURL(link.href) // 释放url document.body.removeChild(link) // 释放标签 } else { // 其他浏览器 navigator.msSaveBlob(blob, fileName) } } //撤销 function undo(){ if (current >= 0) { current --; updateTools(); } updateButton(); } //刷新按钮状态 function updateButton(){ var undo = document.getElementById("undo"); var redo = document.getElementById("redo"); var save = document.getElementById("save"); if (current >= 0) { save.disabled = false; undo.disabled = false; } else { undo.disabled = true; save.disabled = true; } if (current < aryObject.length - 1) { redo.disabled = false; } else { redo.disabled = true; } } //重做 function redo(){ if (current < aryObject.length - 1) { current ++; updateTools(); } updateButton(); } //选择工具 function clickTool(o, tag){ tool = tag; for(var i = 0; i < tools.children.length; i ++){ tools.children[i].style.border = '1px solid black'; } o.style.border = '2px solid red'; var arrowtype = document.getElementById("arrowtype"); arrowtype.hidden = true; text.hidden = true; text.value = ""; if (tag == 1) { text.hidden = false; text.value = "名称"; } if (tag == 2) { text.hidden = false; text.value = "处理"; } if (tag == 3 || tag == 4){ text.hidden = false; text.value = "数据流"; arrowtype.hidden = false; } if (tag == 5) { text.hidden = false; text.value = "数据存储"; } } //绘制所有工具 function updateTools(context){ canvas.width = canvas.width; var i = 0; while (i <= current){ var ary = aryObject[i].split('|'); var type = parseInt(ary[0]); var ary1 = ary[1].split(','); var x1 = parseFloat(ary1[0]); var y1 = parseFloat (ary1[1]); var x2 = parseFloat(ary1[2]); var y2 = parseFloat(ary1[3]); var size = parseFloat(ary[5]); var atype = parseInt(ary[6]); var color = ary[3]; var bcolor = ary[4]; if(type == 4){ x2 = parseFloat(ary1[ary1.length - 2]); y2 = parseFloat(ary1[ary1.length - 1]); aryPoint = []; var j = 0; while(j < ary1.length){ var x = parseFloat(ary1[j]), y = parseFloat(ary1[j + 1]); aryPoint.push(x + "," + y); j += 2; } } drawTool(cxt, type, x1, y1, x2, y2, color, bcolor, size, atype); drawText(cxt, type, x1, y1, x2, y2, ary[2], color, getDataText(ary[2], current)); i ++; } } //通过计算得到当前数据存储的编号 function getDataText(name, count){ var i = 0, j = 0; //统计包含的数据存储类别 var aryType = []; while(i <= count){ var ary = aryObject[i].split('|'); var type = parseInt(ary[0]); if (type == 5 && aryType.indexOf(ary[2]) < 0) { aryType.push(ary[2]); j ++; } i ++; } if (j > 0){ var l = aryType.length, j = aryType.indexOf(name); if (l > 1){ if (j < 0) return "D" + (l + 1); else return "D" + (j + 1); } } return ''; } //保存当前的绘制 function pushTool(type, x1, y1, x2, y2, text, color, bcolor, size, arrowtype = 0){ var ary = []; var i = 0; while(i <= current){ ary.push(aryObject[i]); i ++; } aryObject = ary; var s = type + '|' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + '|' + text + '|' + color + '|' + bcolor + '|' + size + '|' + arrowtype; if(type == 4){ s = type + '|'; i = 0; while(i < aryPoint.length){ if (i > 0) s += ","; s += aryPoint[i]; i ++; } s = s + '|' + text + '|' + color + '|' + bcolor + '|' + size + '|' + arrowtype; } aryObject.push(s); current ++; updateButton(); } //绘制文本 function drawText(context, type, x1, y1, x2, y2, text, color, dataText = ''){ if (text == "" || context == null || type < 1) return; switch(type){ case 1: case 2: case 5:{ //绘制开始框 //计算宽高 var l = x1, t = y1; var w = x2 - x1, h = y2 - y1; if (w < 0) { l = x2; w = x1 - x2; } if (h < 0){ t = y2; h = y1 - y2; } context.font = "10pt SimSun"; //updateFont(context, w, h, text, 100); var w1 = getFontWidth(context, text); var h1 = getFontHeight(context, text); context.fillStyle = color; context.fillText(text, l + (w - w1) / 2, t + h1 / 4 + h / 2); //如果是数据库,还需要输出标号 if (type == 5){ w1 = getFontWidth(context, dataText); h1 = getFontHeight(context, dataText); context.fillText(dataText, l + (w * 0.2 - w1) / 2, t + h1 / 4 + h / 2); } break; } case 3:{ //绘制箭头 //计算文字大小 context.font = "10pt SimSun"; var w = getFontWidth(context, text); var h = getFontHeight(context, text); //获得输出坐标 var x = (x2 + x1) / 2; var y = (y2 + y1) / 2; context.fillStyle = color; context.fillText(text, x, y); break; } case 4:{ //绘制箭头 //计算文字大小 context.font = "10pt SimSun"; var w = getFontWidth(context, text); var h = getFontHeight(context, text); //获得输出坐标 var px1 = x1, py1 = y1; var i = 1; var pp = 0; while(i < aryPoint.length) { var ary = aryPoint[i].split(','); var px = parseFloat(ary[0]), py = parseFloat(ary[1]); var pp1 = 2; if (Math.abs(px - px1) > Math.abs(py - py1)) pp1 = 1; if (pp != 0 && pp1 != pp) break; pp = pp1; px1 = px; py1 = py; i ++; } var x = (px1 + x1) / 2; var y = (py1 + y1) / 2; context.fillStyle = color; context.fillText(text, x, y); break; } } } //绘制当前工具 function drawTool(context, type, x1, y1, x2, y2, color, bcolor, size, atype = 0){ if (context == null || type < 1) return; switch(type){ case 1:{ //绘制外部实体框 //计算宽高 var l = x1, t = y1; var w = x2 - x1, h = y2 - y1; if (w < 0) { l = x2; w = x1 - x2; } if (h < 0){ t = y2; h = y1 - y2; } context.strokeStyle = color; context.lineWidth = size; context.beginPath(); context.moveTo(l, t); context.lineTo(l + w, t); context.lineTo(l + w, t + h); context.lineTo(l, t + h); context.lineTo(l, t); context.closePath(); context.fillStyle = bcolor; context.fill(); context.stroke(); break; } case 2:{ //绘制处理框 //计算宽高 var l = x1, t = y1; var w = x2 - x1, h = y2 - y1; if (w < 0) { l = x2; w = x1 - x2; } if (h < 0){ t = y2; h = y1 - y2; } context.strokeStyle = color; context.lineWidth = size; var r = w * 0.2; if (h < w) r = h * 0.2; context.beginPath(); context.moveTo(l + r, t); context.arcTo(l + w, t, l + w, t + h, r); context.arcTo(l + w, t + h, l, t + h, r); context.arcTo(l, t + h, l, t, r); context.arcTo(l, t, l + w, t, r); context.closePath(); context.fillStyle = bcolor; context.fill(); context.stroke(); break; } case 3:{ //绘制连接箭头 //计算箭头方向 var ll = 10, ss = 30; var a = Math.atan2((y2 - y1), (x2 - x1)); var x3 = x2 - ll * Math.cos(a + ss * Math.PI/180); var y3 = y2 - ll * Math.sin(a + ss * Math.PI/180); var x4 = x2 - ll * Math.cos(a - ss * Math.PI/180); var y4 = y2 - ll * Math.sin(a - ss * Math.PI/180); context.strokeStyle = color; context.lineWidth = size; context.beginPath(); context.moveTo(x1, y1); context.lineTo(x2, y2); context.lineTo(x3, y3); context.moveTo(x2, y2); context.lineTo(x4, y4); if (atype == 2){ a = Math.atan2((y1 - y2), (x1 - x2)); x3 = x1 - ll * Math.cos(a + ss * Math.PI/180); y3 = y1 - ll * Math.sin(a + ss * Math.PI/180); x4 = x1 - ll * Math.cos(a - ss * Math.PI/180); y4 = y1 - ll * Math.sin(a - ss * Math.PI/180); context.moveTo(x1, y1); context.lineTo(x3, y3); context.moveTo(x1, y1); context.lineTo(x4, y4); } context.closePath(); context.stroke(); break; } case 4:{ //绘制折线连接箭头 if (aryPoint.length < 3) break; var ary = aryPoint[aryPoint.length - 2].split(','); var px1 = parseFloat(ary[0]), py1 = parseFloat(ary[1]); ary = aryPoint[aryPoint.length - 1].split(','); var px2 = parseFloat(ary[0]), py2 = parseFloat(ary[1]); if (Math.abs(px2 - px1) > Math.abs(py2 - py1)) py1 = py2; else px1 = px2; //计算箭头方向 var ll = 10, ss = 30; var a = Math.atan2((py2 - py1), (px2 - px1)); var x3 = px2 - ll * Math.cos(a + ss * Math.PI/180); var y3 = py2 - ll * Math.sin(a + ss * Math.PI/180); var x4 = px2 - ll * Math.cos(a - ss * Math.PI/180); var y4 = py2 - ll * Math.sin(a - ss * Math.PI/180); context.strokeStyle = color; context.lineWidth = size; context.beginPath(); context.lineTo(px2, py2); context.lineTo(x3, y3); context.moveTo(px2, py2); context.lineTo(x4, y4) context.moveTo(px2, py2); context.lineTo(px1, py1); var i = aryPoint.length - 3; var px = px1, py = py1; while(i >= 0){ ary = aryPoint[i].split(','); px2 = parseFloat(ary[0]), py2 = parseFloat(ary[1]); if (Math.abs(px2 - px1) > Math.abs(py2 - py1)) py2 = py1; else px2 = px1; context.moveTo(px1, py1); context.lineTo(px2, py2); px = px1; py = py1; px1 = px2; py1 = py2; i --; } if (atype == 2){ px1 = px; py1 = py; a = Math.atan2((py2 - py1), (px2 - px1)); x3 = px2 - ll * Math.cos(a + ss * Math.PI/180); y3 = py2 - ll * Math.sin(a + ss * Math.PI/180); x4 = px2 - ll * Math.cos(a - ss * Math.PI/180); y4 = py2 - ll * Math.sin(a - ss * Math.PI/180); context.moveTo(px2, py2); context.lineTo(x3, y3); context.moveTo(px2, py2); context.lineTo(x4, y4) } context.closePath(); context.stroke(); break; } case 5:{ //绘制数据存储 //计算宽高 var l = x1, t = y1; var w = x2 - x1, h = y2 - y1; if (w < 0) { l = x2; w = x1 - x2; } if (h < 0){ t = y2; h = y1 - y2; } context.strokeStyle = color; context.lineWidth = size; context.beginPath(); context.moveTo(l, t); context.lineTo(l + w, t); context.lineTo(l, t); context.lineTo(l, t + h); context.lineTo(l + w, t + h); context.moveTo(l + w * 0.2, t); context.lineTo(l + w * 0.2, t + h); context.closePath(); context.stroke(); break; } } } //计算当前字体下的文本宽度 function getFontWidth(context, txt) { const metrics = context.measureText(txt); const actual = Math.abs(metrics.actualBoundingBoxLeft ?? 0) + Math.abs(metrics.actualBoundingBoxRight ?? 0); const width = Math.max(metrics.width, actual); return width; } //计算当前字体下的文本高度 function getFontHeight(context, txt) { const metrics = context.measureText(txt); const fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent; //所有字在这个字体下的高度 //const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; return fontHeight; } //自动调整字体大小 function updateFont(context, w, h, text, size){ context.font = size + "pt SimSun"; w1 = getFontWidth(context, text) * 2; h1 = getFontHeight(context, text) * 2; var s = size; while (w1 > w || h1 > h){ s = s - 1; context.font = s + "pt SimSun"; w1 = getFontWidth(context, text) * 2; h1 = getFontHeight(context, text) * 2; } return s; }