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.

483 lines
15 KiB

/**
* E-R图绘制方法集
* 作者刘广文
* 邮箱liugw@imut.edu.cn
* 版本20230819
* 功能基于canvas实现E-R图的绘制
*/
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=\"entity\" onclick=\"clickTool(this, 1);\" title=\"绘制实体\">实体</div>"
+ "<div id=\"property\" onclick=\"clickTool(this, 2);\" title=\"绘制属性\">属性</div>"
+ "<div id=\"link1\" onclick=\"clickTool(this, 3);\" title=\"绘制直线\"></div>"
+ "<div id=\"link2\" onclick=\"clickTool(this, 4);\" title=\"绘制折线\"></div>"
+ "<div id=\"relation\" onclick=\"clickTool(this, 5);\" title=\"绘制联系\">联系</div>"
+ "<div id=\"key\" onclick=\"clickTool(this, 6);\" title=\"绘制主键\">主键</div>";
}
//鼠标抬起响应动作
function myMouseUp(e, page){
flag=false;
pushTool(tool, mouseX, mouseY, mouseX1, mouseY1, text.value, color.value, bcolor.value, size.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);
}
drawTool(cxt, tool, mouseX, mouseY, mouseX1, mouseY1, color.value, bcolor.value, size.value);
}
}
//鼠标按下响应动作
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 = 'erdiagram.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';
text.hidden = true;
text.value = "";
if (tag == 1 || tag == 2 || tag == 5 || tag == 6)
{
text.hidden = false;
text.value = "名称";
}
if (tag == 2)
{
text.hidden = false;
text.value = "名称";
}
if (tag == 3 || tag == 4){
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 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);
drawText(cxt, type, x1, y1, x2, y2, ary[2], color);
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;
var s = type + '|' + x1 + ',' + y1 + ',' + x2 + ',' + y2 + '|' + text + '|' + color + '|' + bcolor + '|' + size;
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;
}
aryObject.push(s);
current ++;
updateButton();
}
//绘制文本
function drawText(context, type, x1, y1, x2, y2, text, color){
if (text == "" || context == null || type < 1) return;
switch(type){
case 1:
case 2:
case 5:
case 6:{
//绘制开始框
//计算宽高
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";
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 == 6)
{
cxt.moveTo(l + (w - w1) / 2, t + h1 / 4 + h / 2);
cxt.lineTo(l + (w + w1) / 2, t + h1 / 4 + h / 2);
cxt.stroke();
}
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){
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 3:{
//绘制直线
context.strokeStyle = color;
context.lineWidth = size;
context.moveTo(x1, y1);
context.lineTo(x2, y2);
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;
context.strokeStyle = color;
context.lineWidth = size;
context.moveTo(px2, py2);
context.lineTo(px1, py1);
var i = aryPoint.length - 3;
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);
px1 = px2;
py1 = py2;
i --;
}
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 + h / 2);
context.lineTo(l + w / 2, t);
context.lineTo(l + w, t + h / 2);
context.lineTo(l + w / 2, t + h);
context.lineTo(l, t + h / 2);
context.closePath();
context.fillStyle = bcolor;
context.fill();
context.stroke();
break;
}
case 2:
case 6:{
//绘制属性
//计算宽高
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.4;
if (h < w)
r = h * 0.4;
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;
}
}
}
//计算当前字体下的文本宽度
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;
}