// contains an array of tables.
var tableArray = [];
// App constants all up top
const GLOBAL_SCALE = 0.7;
const SHOW_HELP = true; // set to false to have the help turned off
const SHADOW = 'rgba(0,0,0,0.8)';
const WHITE = "white";
const TABLE_REFRESH_DELAY = 50; // Time in millisecond befor updating DOM for table add and remove
const FONT = {
face : "px Arial",
size : Math.max(10,18 * GLOBAL_SCALE),
fill : WHITE,
};
const TABLE = {
width : 223 * GLOBAL_SCALE, // size of table
height : 314 * GLOBAL_SCALE,
tables : document.getElementById("tables"),
image : { // table image styles
shadow : SHADOW,
shadowBlur : 20 * GLOBAL_SCALE,
fill : "#2e3f73",
lines : WHITE,
font : FONT,
cursor : "default",
},
empty : { // empty table styles
inset : 30 * GLOBAL_SCALE, // amount box is inset
lines : 'rgba(255,255,255,0.5)',
lineWidth : 8 * GLOBAL_SCALE,
shadow : SHADOW,
shadowBlur : 20 * GLOBAL_SCALE,
font : FONT,
cursor : "pointer",
highlightAmount : 0.3, // amount to highlight empty table when mouse over 0 none 1 full
},
arrow : { // arrow styles
width : 15 * GLOBAL_SCALE, // arrow width
shadow : SHADOW,
shadowBlur : 10 * GLOBAL_SCALE,
// custom cursor
cursor : "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADcAAAAVCAYAAADiv3Z7AAACtUlEQVRYha2Ye1NTMRDFf/KqLY/SlvIolAJFqSKOoqI4o+P3/1LXP3oi23XvTUrpzJm52Zxs9mRzczeFl/u9KkDp2FV5xb/IURT42hIoGbcsp1hMGrxegw1hU9gCWsDrDFriWrTc2FYDL/K1qZiS2KywdRO0DcAiTdIBtoEdYFfYC7Arzo7426a96+B53n/idBTDlha7VmAStiGyD9wjiegCPWAAHABDg0NhqL6B0BcGZpyF5fQM98D52lcc7SaBXlhbArrGeRTQEDgCKoNzwdsq4Aw4BUZqn8qWeGOhMryRGWt9jrRwfSMwbdF/72F6SFuxLXLPORppwspMPgYq+0v93mb63rh2OFbPU9sO5hlrcXtKRstnz2atxXwb9oCqEhYmWrRdR4F6mxdQIzqyfWjwOQOutMgHzF+RlL0FcTZrXaV77MU4YZ+EXIC/InuTuIL+B809Ay6UvX3mZ4TdmgviOiIdARPgxooywr4BX4TGbRkFvoo40/4MvHPi/HtXK+5CKxOJ+y6BXwsC/ePav18gcz+AeyeuS2ZbenFN2zJlLxuUa4fbtIkTtFfOnBfzENjum8TZbWQ4j5nt9jPgPEY+KXzn1ng6UPaYnz7p+1JphW7R6WVsM6FS/x1Ph41d5dT+KB+3at/JVrn+9/Jf6fnWjEm4ofC0jD4Fhxo4kZOpwxVwaTBl/g17q4lnDjfqnxp/17IlXMt+qYxc6NnyLafoO1f3ER8Cxyx+xG3lcKL+E9N/pknPtTATPY/VNwr8nbFYvRw7nDj+qWzZCsVnr6T8snVfj//rv1SaRbVlPxhj0Wf+/lhE3MTL1paRwFzh7Kv2qMqPbgUR3/vsOET+l7oVWIElV56Su9ky9znvczPjf+n7nBUZ3YKjW/FzbuO5MXV/U6x8E68TmrP5vuf+h1IaT5b7F+ZaSjBzrT+rAAAAAElFTkSuQmCC') 10 11, pointer",
fill : "#ffb900",
highlight : "#ffdc44",
lineWidth : 1,
line : "#ffdc44",
lineHigh : "#ffed55",
head : 30 * GLOBAL_SCALE, // arrow head width
minSize : 5, // min size arrow can be if smaller then arrow is not created
},
DOM : { // variouse dom setting for table canvas and div tags
display : "inline-block",
canvasClass : "table",
zIndex : 1,
},
closeIcon : { // styles for reandering and display close icon
size : 32 * GLOBAL_SCALE,
fill : "red",
lines : WHITE,
lineWidth : Math.max(1,2 * GLOBAL_SCALE),
shadow : SHADOW,
shadowBlur : 20 * GLOBAL_SCALE,
cursor : "pointer",
pos : {
x : 1, // as fractions
y : 0,
}
},
help : { // text help
empty : "Click here to|add a new table".split("|"),
active : "Click to drag arrows".split("|"),
activeArrow : "Right click on arrow|to remove it".split("|"),
closeTable : "To close table|move to top right|click Close Icon".split("|"),
}
}
const MOUSE = { // event contains a list of mouse event to listen to
buttonMasks : [1, 2, 4, 6, 5, 3],
events : "mousemove,mousedown,mouseup,mouseout,mouseover,contextmenu".split(","),
}; // contextmenu is included as that needs to be blocked for right button events
var helpItemsUsed = {
empty : false,
active : false,
activeArrow : false,
closeTable : false,
};
const turnOffHelp = function(){
helpItemsUsed.empty = true;
helpItemsUsed.active = true;
helpItemsUsed.activeArrow = true;
helpItemsUsed.closeTable = true;
};
if(!SHOW_HELP){turnOffHelp();};
// returns distance of point p to line segment x, y,xx,yy
const distFromLine = function(px,py,x,y,xx,yy){
var vx,vy,pvx,pvy,lx,ly,u;
vx = xx - x;
vy = yy - y;
pvx = px - x;
pvy = py - y;
u = (pvx * vx + pvy * vy)/(vy * vy + vx * vx);
if(u >= 0 && u <= 1){
lx = vx * u;
ly = vy * u;
return Math.sqrt(Math.pow(ly - pvy,2) + Math.pow(lx - pvx,2));
}
// closest point past ends of line so get dist to closest end
return Math.min(
Math.sqrt(Math.pow(xx - px,2)+ Math.pow(yy - py,2)),
Math.sqrt(Math.pow(x - px,2)+ Math.pow(y - py,2))
);
}
// set up functions create images and do other general setup
function setupContext(ctx,descript){ // sets common context settings
ctx.shadowBlur = descript.shadowBlur;
ctx.shadowColor = descript.shadow;
ctx.strokeStyle = descript.lines;
ctx.fillStyle = descript.fill;
ctx.lineWidth = descript.lineWidth;
ctx.lineCap = "round";
if(descript.font){
ctx.font = descript.font.size + descript.font.face;
}
}
function createTableImage() { // create image of table but why write a comment when the function tells it all???
var table = document.createElement("canvas");
table.width = TABLE.width;
table.height= TABLE.height;
var ctx = table.getContext("2d");
setupContext(ctx,TABLE.image);
var scaleX = table.width/223; /// get the scale compared to original layout
var scaleY = table.height/314; /// get the scale compared to original layout
ctx.fillStyle = TABLE.image.fill;
ctx.fillRect(35.25 * scaleX, 20 * scaleY, 152.5 * scaleX, 274 * scaleY);
ctx.fillStyle = TABLE.image.lines; // lines
ctx.fillRect(111.35 * scaleX, 20 * scaleY, 0.3, 274 * scaleY); // middle line
ctx.fillRect(35.25 * scaleX, 20 * scaleY, 2, 274 * scaleY); // lift side
ctx.fillRect(185.75 * scaleX, 20 * scaleY, 2, 274 * scaleY); // right side
ctx.fillRect(35.25 * scaleX, 20 * scaleY, 152.5 * scaleX, 2); // top base line
ctx.fillRect(35.25 * scaleX, 292 * scaleY, 152.5 * scaleX, 2); // bottom base line
ctx.fillRect(20 * scaleX, 156 * scaleY, 183 * scaleX, 2); // net
return table
}
function createEmptyImage() { // empty table image
var i = TABLE.empty.inset;
var image = document.createElement("canvas");
var w = image.width = TABLE.width;
var h = image.height = TABLE.height;
var ctx = image.getContext("2d");
setupContext(ctx,TABLE.empty);
ctx.strokeRect(i, i, w - i * 2, h - i * 2);
ctx.beginPath();
ctx.moveTo(i * 2, i * 2);
ctx.lineTo(w - i * 2, h - i * 2);
ctx.moveTo(i * 2, h - i * 2);
ctx.lineTo(w - i * 2, i * 2);
ctx.stroke();
return image
}
function createCloseImage() { // create close icon
var S = TABLE.closeIcon.size;
var s = S * 0.5;
var c = s * 0.4; // cross dist from center
var sb = TABLE.closeIcon.shadowBlur;
var l = TABLE.closeIcon.lineWidth;
var image = document.createElement("canvas");
// Image must include shadowblur
image.width = S+sb; // add blur to size
image.height= S+sb;
var ctx = image.getContext("2d");
setupContext(ctx,TABLE.closeIcon);
ctx.beginPath();
var cx = s + sb/2; // add half blur to get center
var cy = s + sb/2;
ctx.arc(cx, cy, s - l, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(cx - c, cy - c)
ctx.lineTo(cx + c, cy + c)
ctx.moveTo(cx - c, cy + c)
ctx.lineTo(cx + c, cy - c)
ctx.stroke();
return image
}
// create the images
var tableImage = createTableImage();
var closeIcon = createCloseImage();
var emptyTableImage = createEmptyImage();
// draws a arrow a is the arrow object
function drawArrow(ctx,a){
var s = TABLE.arrow; // get arrow style
var vx,vy;
var x,y;
x = a.x;
y = a.y;
vx = a.xx-x;
vy = a.yy-y;
var dir = Math.atan2(vy,vx);
var len = Math.sqrt(vx * vx + vy * vy);
// ctx.save();
ctx.setTransform(1,0,0,1,x,y);
ctx.rotate(dir);
var w = s.width/2;
var h = Math.min(len,s.head); // ensure arrow head no bigger than arrow length
h /=2;
if(a.highlight){
ctx.fillStyle = s.highlight;
ctx.strokeStyle = s.lineHigh;
}else{
ctx.fillStyle = s.fill;
ctx.strokeStyle = s.line;
}
ctx.lineWidth = s.lineWidth;
ctx.save();
ctx.shadowBlur = s.shadowBlur;
ctx.shadowColor = s.shadow;
ctx.beginPath();
ctx.moveTo(0,-w/2);
ctx.lineTo(len-h-h,-w);
ctx.lineTo(len-h-h,-h);
ctx.lineTo(len,0);
ctx.lineTo(len-h-h,h);
ctx.lineTo(len-h-h,w);
ctx.lineTo(0,w/2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
}
// display help text for table
function drawHelpText(ctx,text,style){
ctx.font = style.font.size + style.font.face;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var i,len;
len = text.length;
var y = ctx.canvas.height/2 - len * style.font.size * 1.2;
var yy = y + 1;
ctx.strokeStyle = "#000";
ctx.lineWidth = 2;
for(i = 0; i < len; i++){
ctx.strokeText(text[i], ctx.canvas.width/2 + 1, yy);
yy += TABLE.empty.font.size * 1.2;
}
ctx.fillStyle = style.font.fill;
for(i = 0; i < len; i++){
ctx.fillText(text[i], ctx.canvas.width/2, y);
y += TABLE.empty.font.size * 1.2;
}
}
//------------------------------------------------------------
// functions for table
function drawClose(){ // draws close icon. Fades in the close mouse is
var ctx = this.ctx;
var w = closeIcon.width;
var grow = w * 0.1;
var x = (this.width - w) * TABLE.closeIcon.pos.x ;
var y = (this.height - w) * TABLE.closeIcon.pos.y ;
x += w/2; // get icon center
y += w/2;
var dist = Math.sqrt(Math.pow(this.mouse.x - x, 2) + Math.pow(this.mouse.y - y, 2));
if(dist < TABLE.closeIcon.size/2){
this.mouseOverClose = true;
}else{
this.mouseOverClose = false;
}
x -= w/2; // back to icon top left
y -= w/2;
ctx.globalAlpha = 1-(Math.min(100,(dist - w * 2))/100);
if(this.mouseOverClose){
ctx.drawImage(closeIcon,x-grow,y-grow,w + grow * 2,w + grow * 2);
}else{
ctx.drawImage(closeIcon,x,y);
}
ctx.globalAlpha = 1;
}
function drawEmpty(){ // draw empty table and handle click on empty table
var ctx = this.ctx;
ctx.drawImage(emptyTableImage,0,0);
if(this.mouse.over){
ctx.globalCompositeOperation = "lighter";
ctx.globalAlpha = TABLE.empty.highlightAmount;
ctx.drawImage(emptyTableImage,0,0);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
if(!helpItemsUsed.empty){ // show help is the help action has not yet been done
drawHelpText(ctx,TABLE.help.empty,TABLE.empty);
}
this.cursor = TABLE.empty.cursor;
if(this.mouse.button & 1){ // bit field
this.buttonDown = true;
}else if(this.buttonDown){
this.active = true;
setTimeout(addTable,TABLE_REFRESH_DELAY);
this.buttonDown = false;
helpItemsUsed.empty = true; // flag this help as not needed as user has complete that task
}
}else{
this.cursor = "default";
}
}
function drawTable(){ // darw the table all states
var ctx = this.ctx;
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
if(this.active){
ctx.drawImage(tableImage,0,0);
if(this.mouse.over){
if(!this.dragging){ // Dont draw close icon while draggin
this.drawCloseIcon();
}
if(this.mouseOverClose && ! this.dragging){ // if not dragging and mouse over close
this.cursor = TABLE.closeIcon.cursor; // set cursor
if(this.mouse.button & 1){ // bit field is mouse left down
this.buttonDown = true;
}else if(this.buttonDown){ // only close if mouse moves up while over close.
this.active = false;
helpItemsUsed.closeTable = true;
this.buttonDown = false;
setTimeout(updateTables,TABLE_REFRESH_DELAY);
}
}else{ // not over close
// if near a arrow and mouse button right is down delete the arrow
if(this.closestArrowIndex > -1 && (this.mouse.button & 4) === 4){ // but field Only button right down
this.arrows.splice(this.closestArrowIndex,1);
this.closestArrowIndex = -1;
this.mouse.button = 0; // turn mouse click off
helpItemsUsed.activeArrow = true; // flag arrow delete help as used
}else // if not near line or close then check for mouse left
if(this.mouse.button & 1){ // bit field if down start dragging new arroe
if(!this.dragging){ // Start of drag create arrow
this.arrows.push({
x: this.mouse.x,
y: this.mouse.y,
xx : this.mouse.x,
yy : this.mouse.y,
});
this.currentArrow = this.arrows[this.arrows.length-1];
this.dragging = true;
}else{ // during drag move arrow endpoint
helpItemsUsed.active = true; // flag arrow help as used
this.currentArrow.xx = this.mouse.x;
this.currentArrow.yy = this.mouse.y;
}
}else{ // mouse up
if(this.dragging){ // is dragging then must be a arrow
// if arrow added is smaller than 2 pixels then remove it;
if(Math.abs(this.currentArrow.xx-this.currentArrow.x) < TABLE.arrow.minSize && Math.abs(this.currentArrow.y-this.currentArrow.yy) < TABLE.arrow.minSize){
this.arrows.length -= 1;
}
this.currentArrow = null;
this.dragging = false;
}
}
this.cursor = TABLE.image.cursor; // set cursor tp table standard
}
}
if(this.closestArrowIndex > -1 && ! this.dragging){ // is mouse near arrow
this.cursor = TABLE.arrow.cursor; // yes set cursor for arrow
}
// find arrow closest to mouse
var minDist = TABLE.arrow.width; // this sets the max distance mouse can be for it to highlight an arrow
var dist = 0;
this.closestArrowIndex = -1;
for(var i = 0; i < this.arrows.length; i++){ // test all arrow
var a = this.arrows[i];
drawArrow(ctx,a); // draw the arrow
a.highlight = false; // turn off highlight
dist = distFromLine(this.mouse.x,this.mouse.y,a.x,a.y,a.xx,a.yy); // get distance from mouse
if(dist < minDist){ // is closer than any other arrow
this.closestArrowIndex = i; // yes remember the index
minDist = dist;
}
}
if(this.closestArrowIndex > -1 && this.mouse.over){ // is a arror close to mouse
this.arrows[this.closestArrowIndex].highlight = true; // highlight it
}
ctx.setTransform(1,0,0,1,0,0); // reset transform after arrows drawn
// show help
if(this.mouse.over){
if(this.arrows.length === 0 && !helpItemsUsed.active){
drawHelpText(ctx,TABLE.help.active,TABLE.image);
}else
if(this.closestArrowIndex > -1 && !helpItemsUsed.activeArrow){
drawHelpText(ctx,TABLE.help.activeArrow,TABLE.image);
}else
if(this.closestArrowIndex === -1 && !helpItemsUsed.closeTable){
drawHelpText(ctx,TABLE.help.closeTable,TABLE.image);
}
}
}else{
this.drawEmpty();
}
}
// renders a table. Stops rendering if the mouse is not over
function tableUpdate(){
if(this.mouse.over){
this.updating = true;
requestAnimationFrame(this.update);
}else{
this.buttonDown = false; // turn of button if dragged off
this.div.style.cursor = "default";
this.updating = false;
this.draw(); // draw another time. This alows for the visual state to be correct
}
this.draw();
this.div.style.cursor = this.cursor;
}
// Mousecallback starts a table rendering if not allready doing so.
function mouseInOutCallback(){
if(this.mouse.over){
if(!this.updating){
this.update();
}
}else{
this.div.style.cursor = "default";
}
}
// function to handle mouse events
function mouseEvent(e) {
var m =this; // lazy programer short cut
var t = e.type;
var bounds = m.element.getBoundingClientRect();
m.x = e.clientX - bounds.left;
m.y = e.clientY - bounds.top;
if (t === "mousedown") {
m.button |= MOUSE.buttonMasks[e.which-1];
} else if (t === "mouseup") {
m.button &= MOUSE.buttonMasks[e.which + 2];
} else if (t === "mouseout") {
m.button = 0;
m.over = false;
m.table.mouseOver();
} else if (t === "mouseover") {
m.over = true;
m.table.mouseOver();
}
e.preventDefault();
}
// create the mouse inteface for a table
function createMouse(table){
var mouse = {
x : 0,
y : 0,
over : false,
table : table,
element : table.div,
button : 0,
};
mouse.event = mouseEvent.bind(mouse);
mouse.start = function(){
MOUSE.events.forEach(n => { this.element.addEventListener(n, this.event); });
}
mouse.remove = function(){
MOUSE.events.forEach(n => { this.element.removeEventListener(n, this.event); });
}
return mouse;
}
function createAddTable(){ // Creates a table. Tables default in inactive
var table = {};
var div = document.createElement("div");
div.style.width = TABLE.width+ "px";
div.style.height = TABLE.height + "px";
div.style.display = TABLE.DOM.display;
var canvas = document.createElement("canvas");
canvas.width = TABLE.width;
canvas.height = TABLE.height;
canvas.className = TABLE.DOM.tableClass
canvas.style.zIndex = TABLE.DOM.zIndex;
var ctx = canvas.getContext("2d");
table.div = div;
table.canvas = canvas;
table.ctx = ctx;
table.arrows = [];
table.width = TABLE.width;
table.height = TABLE.height;
table.mouseOverClose = false
table.drawCloseIcon = drawClose;
table.draw = drawTable;
table.dragging = false;
table.active = false;
table.update = tableUpdate.bind(table);
table.mouseOver = mouseInOutCallback; // called by mouseEvent when mouse over out
table.drawEmpty = drawEmpty.bind(table);
table.dead = false; // when removed and not needed it is dead and can then be removed from table array
table.updating = false; // true is animation requests are happening
div.appendChild(canvas); // add canvas
table.mouse = createMouse(table);
table.draw();
return table;
}
function removeTable(table){ // remove table from dom
table.mouse.remove(); // deactivate moue events
TABLE.tables.removeChild(table.div); // remove from DOM
table.dead = true;// flag as dead to be removed from table array
}
function addTable(){ // Adds a table to table array and DOM
var table = createAddTable(); // create new table
TABLE.tables.appendChild(table.div); // add to the dom
table.mouse.start(); // start the mouse
tableArray.push(table); // add to table array
return table;
}
function updateTables(){ // Updates tables. Removes any dead tables from table array
var closeTables = [];
closeTables = tableArray.filter(t => !t.active);
while(closeTables.length > 1){
removeTable(closeTables.shift());
}
for(var i = 0; i < tableArray.length; i ++){
if(tableArray[i].dead){
tableArray.splice(i,1);
i -= 1;
}
}
}
addTable();
body {
background-color: #982439;
}
#table {
padding: 10px;
}
canvas {
\t position: absolute;
}
<div id="tables">
</div>
看到這個以前的[Q&A]( http://stackoverflow.com/questions/29692134/how-to-delete-only-a-line-from-the-canvas-not-all-the-drawings/29704300#29704300)找到哪條線最接近你的老鼠。然後按照您的設計要求處理該線。 – markE