diff --git a/ipograph.js b/ipograph.js
new file mode 100644
index 0000000..126851d
--- /dev/null
+++ b/ipograph.js
@@ -0,0 +1,416 @@
+/**
+ * IPO图绘制方法集
+ * 作者:刘广文
+ * 邮箱:liugw@imut.edu.cn
+ * 版本:20230813
+ * 功能:基于canvas实现IPO图的绘制
+ */
+var tool = 1;
+var current = -1;
+var flag=false;
+var mouseX= 0;
+var mouseY= 0;
+var mouseX1= 0;
+var mouseY1= 0;
+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;
+
+//加载工具
+function loadTools(target){
+ target.innerHTML = "
文字框
"
+ + "箭头
"
+ + "";
+}
+//确定添加
+function okTool(){
+ pushTool(tool, x, y, x + w, y + h, myText.value, color.value, bcolor.value, size.value, fcolor.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 (tool == 1 && !drawn)
+ {
+ //
+ drawn = true;
+ 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();
+ }
+ else if (tool == 2){
+ pushTool(tool, mouseX, mouseY, mouseX1, mouseY1, text.value, color.value, bcolor.value, size.value, fcolor.value);
+ }
+ updateTools(cxt);
+}
+//鼠标移动响应动作
+function myMouseMove(e, page){
+ mouseX1= e.pageX-page.offsetLeft;
+ mouseY1= e.pageY-page.offsetTop;
+ if(flag){
+ updateTools(cxt);
+ if (tool == 2 || !drawn)
+ drawTool(cxt, tool, mouseX, mouseY, mouseX1, mouseY1, "#000000", "#FFFFFF", 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 = 'ipograph.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)
+ {
+ var ary = aryObject[current].split('|');
+ if (parseInt(ary[0]) == 2){
+ current --;
+ updateTools();
+ updateButton();
+ return;
+ }
+ 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 (aryObject.length > 0 && current < aryObject.length - 1)
+ {
+ current ++;
+ var ary = aryObject[current].split('|');
+ if (parseInt(ary[0]) == 2 || current == aryObject.length - 1)
+ {
+ updateTools();
+ updateButton();
+ return;
+ }
+ current ++;
+ while(current < aryObject.length)
+ {
+ 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;
+}
+
+//绘制所有工具
+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];
+ var fcolor = ary[6];
+ 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], fcolor);
+ yy += getFontHeight(cxt, ary[2]);
+ }
+ else break;
+ i ++;
+ }
+ }
+ else if(type == 2){
+ drawTool(cxt, type, x1, y1, x2, y2, color, bcolor, size);
+ i ++;
+ }
+ }
+}
+//保存当前的绘制
+function pushTool(type, x1, y1, x2, y2, text, color, bcolor, size, fcolor){
+ 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 + '|' + fcolor;
+ 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 + '|' + fcolor;
+ aryObject.push(s);
+ current ++;
+ }
+ 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:{
+ //绘制框型数据字典
+ //计算宽高
+ 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 == 2)
+ text.value = Math.trunc(h / getFontHeight(aryTitle[0]));
+ break;
+ }
+ case 2:{
+ //绘制连接箭头
+ //计算箭头方向
+ 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)
+ 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) {
+ var fontHeight = 20;
+ try{
+ const metrics = context.measureText(txt);
+ fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
+ }
+ catch{
+ fontHeight = 20;
+ }
+ //所有字在这个字体下的高度
+ //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