diff --git a/dataflow.js b/dataflow.js new file mode 100644 index 0000000..7a34d9c --- /dev/null +++ b/dataflow.js @@ -0,0 +1,570 @@ +/** + * 数据流图绘制方法集 + * 作者:刘广文 + * 邮箱: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; +} \ No newline at end of file