You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

570 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 数据流图绘制方法集
* 作者:刘广文
* 邮箱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 = "<div id=\"object\" onclick=\"clickTool(this, 1);\" title=\"绘制外部实体\">源点/终点</div>"
+ "<div id=\"process\" onclick=\"clickTool(this, 2);\" title=\"绘制处理框\">处理</div>"
+ "<div id=\"arrow\" onclick=\"clickTool(this, 3);\" title=\"绘制数据流1\"></div>"
+ "<div id=\"arrow1\" onclick=\"clickTool(this, 4);\" title=\"绘制数据流2\"></div>"
+ "<div id=\"datasrc\" onclick=\"clickTool(this, 5);\" title=\"绘制数据存储\">数据存储</div>"
+ "<select id=\"arrowtype\" title=\"箭头方向\" hidden><option value=\"1\">单向</option><option value=\"2\">双向</option></select>";
}
//鼠标抬起响应动作
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对象来处理数据
// 对于<a>标签,只有 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;
}