|
|
/**
|
|
|
* 数据字典绘制方法集
|
|
|
* 作者:刘广文
|
|
|
* 邮箱: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 = "<div id=\"single\" onclick=\"clickTool(this, 1);\" title=\"绘制方框式数据字典\">方框</div>"
|
|
|
+ "<div id=\"table\" onclick=\"clickTool(this, 2);\" title=\"绘制表格式数据字典\">表格</div>"
|
|
|
+ "<div id=\"myTable\" hidden></div>"
|
|
|
+ "<textarea id=\"myText\" hidden></textarea><input type=\"button\" id=\"okButton\" value=\"确定\" onclick=\"okTool();\" hidden/><input type=\"button\" id=\"cancelButton\" value=\"取消\" onclick=\"cancelTool();\" hidden/>";
|
|
|
}
|
|
|
//确定添加
|
|
|
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 = "<table width=\"100%\"><tr>";
|
|
|
var i = 0;
|
|
|
while(i < aryTitle.length)
|
|
|
{
|
|
|
s += "<td>" + aryTitle[i] + "</td>";
|
|
|
i ++;
|
|
|
}
|
|
|
s += "</tr>";
|
|
|
var j = 0;
|
|
|
while(j < rowcount){
|
|
|
s += "<tr>";
|
|
|
i = 0;
|
|
|
while(i < aryTitle.length)
|
|
|
{
|
|
|
s += ("<td><textarea style=\"width:" + ww + "px;\"></textarea></td>");
|
|
|
i ++;
|
|
|
}
|
|
|
s += "</tr>";
|
|
|
j ++;
|
|
|
}
|
|
|
s += "</table>";
|
|
|
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对象来处理数据
|
|
|
// 对于<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)
|
|
|
{
|
|
|
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;
|
|
|
} |