/** * 数据字典绘制方法集 * 作者:刘广文 * 邮箱:liugw@imut.edu.cn * 版本:20230819 * 功能:基于canvas实现数据字典的绘制 */ var tool = 1; var current = -1; var flag=false; var mouseX= 0; var mouseY= 0; var mouseX1= 0; var mouseY1= 0; var aryValue = []; var aryObject = []; var drawn = false; var myText = null, okButton = null, cancelButton = null; var w = 0, h = 0, x = 0, y = 0, pLeft = 0, pTop = 0; const aryTitle = ["数据存储名称", "属性名称", "数据类型", "描述"] //加载工具 function loadTools(target){ target.innerHTML = "
方框
" + "
表格
" + "" + ""; } //确定添加 function okTool(){ if (tool == 1) pushTool(tool, x, y, x + w, y + h, myText.value, color.value, bcolor.value, size.value); if (tool == 2) { aryValue = []; for(var i = 1; i < myText.children[0].children[0].children.length; i ++){ var row = myText.children[0].children[0].children[i]; for(var j = 0; j < aryTitle.length; j ++) aryValue.push(row.cells[j].children[0].value); } pushTool(tool, x, y, x + w, y + h, "", color.value, bcolor.value, size.value); } cancelTool(); updateTools(cxt, tool, x, y, x + w, y + h, color.value, bcolor.value, size.value); } //取消添加 function cancelTool(){ if (myText != null) { myText.hidden = true; okButton.hidden = true; cancelButton.hidden = true; } drawn = false; } //鼠标抬起响应动作 function myMouseUp(e, page){ flag=false; if (!drawn) { //pushTool(tool, mouseX, mouseY, mouseX1, mouseY1, text.value, color.value, bcolor.value, size.value); drawn = true; if (tool == 1) { if (myText == null) { myText = document.getElementById("myText"); okButton = document.getElementById("okButton"); cancelButton = document.getElementById("cancelButton"); } myText.hidden = false; okButton.hidden = false; cancelButton.hidden = false; x = mouseX; y = mouseY; if (mouseX1 < mouseX) x = mouseX1; if (mouseY1 < mouseY) y = mouseY1; w = Math.abs(mouseX - mouseX1); h = Math.abs(mouseY - mouseY1); var rect = canvas.getBoundingClientRect(); pLeft = rect.left; pTop = rect.top; myText.style.left = (x + pLeft) + "px"; myText.style.top = (y + pTop) + "px"; myText.style.width = w + "px"; myText.style.height = h + "px"; cancelButton.style.left = (x + pLeft + w - 50) + "px"; cancelButton.style.top = (y + pTop + h + 5) + "px"; okButton.style.left = (x + pLeft + w - 110) + "px"; okButton.style.top = (y + pTop + h + 5) + "px"; textWidth = w; textHeight = h; myText.addEventListener("mousemove", function(){ if(myText.clientWidth != w || myText.clientHeight != h){ w = myText.clientWidth; h = myText.clientHeight; cancelButton.style.left = (x + pLeft + w - 50) + "px"; cancelButton.style.top = (y + pTop + h + 5) + "px"; okButton.style.left = (x + pLeft + w - 110) + "px"; okButton.style.top = (y + pTop + h + 5) + "px"; } }); myText.focus(); } if (tool == 2) { if (myText == null) { myText = document.getElementById("myTable"); okButton = document.getElementById("okButton"); cancelButton = document.getElementById("cancelButton"); } myText.hidden = false; okButton.hidden = false; cancelButton.hidden = false; x = mouseX; y = mouseY; if (mouseX1 < mouseX) x = mouseX1; if (mouseY1 < mouseY) y = mouseY1; w = Math.abs(mouseX - mouseX1); h = Math.abs(mouseY - mouseY1); var rect = canvas.getBoundingClientRect(); pLeft = rect.left; pTop = rect.top; myText.style.left = (x + pLeft) + "px"; myText.style.top = (y + pTop) + "px"; myText.style.width = w + "px"; myText.style.height = h + "px"; cancelButton.style.left = (x + pLeft + w - 50) + "px"; cancelButton.style.top = (y + pTop + h + 5) + "px"; okButton.style.left = (x + pLeft + w - 110) + "px"; okButton.style.top = (y + pTop + h + 5) + "px"; textWidth = w; textHeight = h; var rowcount = parseInt(text.value); if (rowcount > 0){ var ww = w / 4 - 10, hh = 25; var s = ""; var i = 0; while(i < aryTitle.length) { s += ""; i ++; } s += ""; var j = 0; while(j < rowcount){ s += ""; i = 0; while(i < aryTitle.length) { s += (""); i ++; } s += ""; j ++; } s += "
" + aryTitle[i] + "
"; myText.innerHTML = s; } myText.focus(); } } updateTools(cxt); } //鼠标移动响应动作 function myMouseMove(e, page){ mouseX1= e.pageX-page.offsetLeft; mouseY1= e.pageY-page.offsetTop; if(flag){ updateTools(cxt); if (!drawn) drawTool(cxt, tool * 10, mouseX, mouseY, mouseX1, mouseY1, color.value, bcolor.value, size.value); } } //鼠标按下响应动作 function myMouseDown(e, page) { mouseX= e.pageX-page.offsetLeft; mouseY= e.pageY-page.offsetTop; flag=true; } //保存 function save(){ var ary = []; var i = 0; while(i <= current){ ary.push(aryObject[i]); i ++; } aryObject = ary; updateButton(); const fileName = 'flowchart.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) { while(current > 0) { var ary = aryObject[current].split('|'); if (parseInt(ary[0]) < 3) break; current --; } 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) { if (current < 0) { if (aryObject.length > 1) current = 1; else current = 0; } else { if (current + 2 < aryObject.length) current += 2; else current = aryObject.length - 1; } while(current < aryObject.length) { var ary = aryObject[current].split('|'); if (parseInt(ary[0]) < 3) { current --; break } if (current == aryObject.length - 1) break; current ++; } updateTools(); } updateButton(); } //选择工具 function clickTool(o, tag){ cancelTool(); myText = null; 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'; text.hidden = true; if (tag == 2) { text.hidden = false; } } //绘制所有工具 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 color = ary[3]; var bcolor = ary[4]; if(type == 1){ drawTool(cxt, type, x1, y1, x2, y2, color, bcolor, size); i ++; var yy = y1; while(i <= current){ ary = aryObject[i].split('|'); type = parseInt(ary[0]); if (type > 2) { drawText(cxt, type, x1, yy, x2, y2, ary[2], color); yy += getFontHeight(cxt, ary[2]); } else break; i ++; } } else if(type == 2){ i ++; var ww = Math.abs(x2 - x1) / aryTitle.length; var hh = getFontHeight(cxt, aryTitle[0]); var j = 0; while(j < aryTitle.length) { drawTool(cxt, type, x1 + j * ww, y1, x1 + (j + 1) * ww, y1 + hh, color, bcolor, size); drawText(cxt, type, x1 + j * ww, y1, x1 + (j + 1) * ww, y1 + hh, aryTitle[j], color); j ++; } var rr = 0, cc = 0, rCount = 0, rMax = 0; var aryText = []; var yy = y1 + hh; while(i <= current){ ary = aryObject[i].split('|'); type = parseInt(ary[0]); if (type > 2) { ary1 = ary[1].split(','); var r = parseInt(ary1[0]), c = parseInt(ary1[1]); if (rr != r) { j = 0; while(j < aryTitle.length) { drawTool(cxt, 2, x1 + j * ww, yy, x1 + (j + 1) * ww, yy + hh * rMax, color, bcolor, size); var jj = 0; for(var k = 0; k < aryText.length; k ++) { if (aryText[k].startsWith(j + "|")) { ary1 = aryText[k].split('|'); drawText(cxt, 2, x1 + j * ww, yy + hh * jj, x1 + (j + 1) * ww, yy + hh * (jj + 1), ary1[1], color); jj ++; } } j ++; } yy += hh * rMax rMax = 0; rCount = 0; aryText = []; } aryText.push(c + "|" + ary[2]); if (c != cc) { if (rMax < rCount) rMax = rCount; rCount = 0; } cc = c; rr = r; rCount ++; } else break; i ++; } if (rCount > 0) { j = 0; while(j < aryTitle.length) { drawTool(cxt, 2, x1 + j * ww, yy, x1 + (j + 1) * ww, yy + hh * rMax, color, bcolor, size); var jj = 0; for(var k = 0; k < aryText.length; k ++) { if (aryText[k].startsWith(j + "|")) { ary1 = aryText[k].split('|'); drawText(cxt, 2, x1 + j * ww, yy + hh * jj, x1 + (j + 1) * ww, yy + hh * (jj + 1), ary1[1], color); jj ++; } } j ++; } } } //i ++; } } //保存当前的绘制 function pushTool(type, x1, y1, x2, y2, text, color, bcolor, size){ var ary = []; var i = 0; while(i <= current){ ary.push(aryObject[i]); i ++; } aryObject = ary; if(type == 1) { var s = type + '|' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + '||' + color + '|' + bcolor + '|' + size; aryObject.push(s); current ++; ary = text.split("\n"); i = 0; while(i < ary.length){ s = type + '0||' + ary[i]; aryObject.push(s); i ++; current++; } } if(type == 2) { var s = type + '|' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + '||' + color + '|' + bcolor + '|' + size; aryObject.push(s); current ++; var j = 0, r = 0, c = 0; while(j < aryValue.length){ ary = aryValue[j].split('\n'); if (c == aryTitle.length) { c = 0; r ++; } i = 0; while(i < ary.length) { s = type + "0|" + r + "," + c + "|" + ary[i]; aryObject.push(s); current++; i ++; } j ++; c ++; } } updateButton(); } //绘制文本 function drawText(context, type, x1, y1, x2, y2, text, color){ if (text == "" || context == null) return; context.fillStyle = color; context.fillText(text, x1, y1 + 10); } //绘制当前工具 function drawTool(context, type, x1, y1, x2, y2, color, bcolor, size){ if (context == null || type < 1) return; switch(type){ case 1: case 10: case 2: case 20:{ //绘制矩形框 //计算宽高 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(); if(type == 20) { text.value = Math.trunc(h / getFontHeight(context, aryTitle[0])); } break; } case 21:{ //绘制矩形框 //计算宽高 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; } var rSize = 25, cSize = w / 4; context.strokeStyle = color; context.lineWidth = size; //context.beginPath(); var yy = t; var rr = 0; while(yy <= t + h){ context.moveTo(l, yy); context.lineTo(l + w, yy); yy += rSize; rr ++; } text.value = rr; var xx = l; while(xx <= l + w){ context.moveTo(xx, t); context.lineTo(xx, t + h); xx += cSize; } //context.closePath(); //context.fillStyle = bcolor; //context.fill(); 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; }