如何使用javascript和Canvas 2D上下文注释线条

How to annotate a line using javascript and Canvas 2D context?

本文关键字:上下文 注释 2D Canvas 何使用 javascript      更新时间:2023-09-26

我需要通过在开始和结束处放置文本来注释一行。我还需要插入文本,而文本所在的行不可见。

有人能告诉我如何在2D画布上下文的行中添加文本吗。

使用setTransform注释行

注释线条最好使用CanvasRenderingContext2D.setTransform(),以便将世界空间(文本)与屏幕空间(绘制线条的位置)对齐。一旦你有了它,那么所有的绘图坐标都将是沿着画布顶部的线。

直线的起点在(0,0)处,直线的终点在等于直线长度的x坐标处。可以使用CanvasRenderingContext2D.textBaseline属性在直线上方、上方和下方绘制。可以使用CanvasRenderingContext2D.textAlign特性绘制与端点和中心对齐的文本。

要在行中有间隙,请使用CanvasRenderingContext2D.measureText函数获取文本宽度并在文本周围绘制线段。

代码示例具有函数transformToLine(ctx, x1, y1, x2, y2),该函数将当前画布变换设置为与行对齐,然后返回行的长度,以便可以使用它添加文本。

我还提供了一些代码,展示了如何在线上、在线中、在线前、在线后、在线上和在线下进行渲染。还有三个实用程序函数可以帮助在行中绘制文本。

transformToLine(ctx, x1, y1, x2, y2)是一个非常方便的函数,它有很多用途,不仅仅用于沿直线绘制文本。就我个人而言,我觉得它非常有用,我把它添加到画布上下文中,而不是作为一个全局函数。

var canvas = document.getElementById("canV"); 
var ctx = canvas.getContext("2d");
// ES6 new math function 
// simple polyfill for IE on others 
var hypot;
if(typeof Math.hypot === 'function'){
    hypot = Math.hypot;
}else{
    hypot = function(x,y){  // Untested 
        return Math.sqrt(Math.pow(x,2)+Math.pow(y,2));
    };
}
// Sets the 2D context tranformation to the line segment x1,y1 to x2, y2 and
// returns the length of the line
function transformToLine(ctx, x1, y1, x2, y2){
    var ang, xd, yd;
    var ang = Math.atan2(y2 - y1, x2 - x1);     // get the line direction
    var xd = Math.cos(ang);                     // get the vector for x axis
    var yd = Math.sin(ang);
    ctx.setTransform(xd, yd, -yd, xd, x1, y1);  // create the trnasform 
    return hypot(x1 - x2, y1 - y2);        // return the lines length
}    
// restores the 2Dcontext transformation to the default
// Use the rather than save and restore if its only the transform that you want
// to save and restore. its a lot quicker.
function transformDefault(ctx){
    ctx.setTransform(1, 0, 0, 1, 0, 0);
}    
// Draws text in a line, drawing the line either side of the text if there is space.
// ctx is the context to be drawn on.
// text is the text
// inset is how many pixels from the start
// x1, y1 and x2, y2 is the line
function textAtStart (ctx, text, inset, x1, y1, x2, y2) {
    var len, textW, lw;
    len = transformToLine(ctx, x1, y1, x2, y2); // set transform and get line length
    ctx.textBaseline = "middle";                // set text over the line
    ctx.textAlign = "left";                     // align left
    textW = ctx.measureText(text).width;        // get the text size
    ctx.fillText(text, inset, 0);               // draw the text
    ctx.beginPath();                            // draw the parts of the line around the text if there is space
    lw = Number(ctx.lineWidth) * 2;             // leave a clearance so the line does not touch the text
    if (inset - lw > 0) {                       // Check for space befor the text
        ctx.moveTo(0, 0);   
        ctx.lineTo(inset - lw, 0);
        if (inset + textW + lw < len) {          // check for space after the text
            ctx.moveTo(inset + textW + lw, 0);
            ctx.lineTo(len, 0);
        }
        ctx.stroke();                           // draw the line
    }
    transformDefault(ctx);                      // restor the transform
}
// Draws text in a line, drawing the line either side of the text if there is space.
// ctx is the context to be drawn on.
// text is the text
// inset is how many pixels from the end
// x1, y1 and x2, y2 is the line
function textAtEnd (ctx, text, inset, x1, y1, x2, y2) {
    var len, textW, lw;
    len = transformToLine(ctx, x1, y1, x2, y2); // set transform and get line length
    ctx.textBaseline = "middle";                // set text over the line
    ctx.textAlign = "right";                    // align left
    textW = ctx.measureText(text).width;        // get the text size
    ctx.fillText(text, len - inset, 0);        // draw the text
    ctx.beginPath();                            // draw the parts of the line around the text if there is space
    lw = Number(ctx.lineWidth) * 2;             // leave a clearance so the line does not touch the text
    if (len - inset - textW - lw > 0) {         // Check for space befor the text
        ctx.moveTo(0, 0);   
        ctx.lineTo(len - inset - textW - lw, 0);
        if (len - inset + lw  < len) {          // check for space after the text
            ctx.moveTo(len - inset + lw, 0);
            ctx.lineTo(len, 0);
        }
        ctx.stroke();                           // draw the line
    }
    transformDefault(ctx);                      // restor the transform
}
// Draws text in a line, drawing the line either side of the text if there is space.
// ctx is the context to be drawn on.
// text is the text
// x1, y1 and x2, y2 is the line
function textAtCenter (ctx, text, x1, y1, x2, y2) {
    var len, textW, lw;
    len = transformToLine(ctx, x1, y1, x2, y2); // set transform and get line length
    ctx.textBaseline = "middle";                // set text over the line
    ctx.textAlign = "center";                   // align center
    textW = ctx.measureText(text).width;        // get the text size
    ctx.fillText(text, len / 2, 0);             // draw the text
    ctx.beginPath();                            // draw the parts of the line around the text if there is space
    lw = Number(ctx.lineWidth) * 2;             // leave a clearance so the line does not touch the text
    if (len / 2 - textW / 2 - lw > 0) {          // Check for space befor the text
        ctx.moveTo(0, 0);   
        ctx.lineTo(len / 2 - textW / 2 - lw, 0);
        if (len / 2 + textW / 2 + lw  < len) {          // check for space after the text
            ctx.moveTo(len / 2 + textW / 2 + lw, 0);
            ctx.lineTo(len, 0);
        }
        ctx.stroke();                           // draw the line
    }
    transformDefault(ctx);                      // restor the transform
}
// Thats all you need to put text on a line.
// below is an example of how it used
// variables and constants. My coding style always has constants upperCase with 
// snake case used for clarity is needed.
const PI2 = Math.PI*2;
var i;
const CW = canvas.width/2;  // center Width CW
const CH = canvas.height/2; // center Height CH
var x1,y1,x2,y2,len,angleText, lengthText;
// set up the ctx 
ctx.font = "12px verdana";
ctx.lineJoin = "round";
// clear the sreen
ctx.clearRect(0,0,canvas.width,canvas.height)
// draw 16 lines in a circle towards the center.
for(i = 0; i < PI2; i += PI2/16){
    // get the start and end location of a line
    x1 = Math.cos(i)*(CW*0.95)+CW;
    y1 = Math.sin(i)*(CH*0.95)+CH;
    x2 = Math.cos(i)*(CW*0.2)+CW;
    y2 = Math.sin(i)*(CH*0.2)+CH;
    
    angleText = "Ang: "+Math.round((360/PI2)*i); // get the aprox angle in deg
    
    
    // set the transformation to the line 
    // This is the secret to drawing alon a line.
    len = transformToLine(ctx,x1,y1,x2,y2);
    
    //If you dont want the line text to be upside down
    // world space is now allong the line with the orign
    // at the line start x1,y1 and the end of the line at
    // world coordinate len,0 with down at 90 clockwise from 
    // the line
    
    lengthText = "Len: "+Math.round(len)
    
    ctx.fillStyle = "black"; //text black
    ctx.strokeStyle = "black"; //line black
    // Draw the angle  text above the line.
    ctx.textAlign = "left"; // align to the start of the line
    ctx.textBaseline = "bottom";
    ctx.fillText(angleText,0,-2);
    
    // draw the length under the line    
    ctx.textBaseline = "top";
    ctx.fillText(lengthText,0,0);
    // Draw end just befor the end of the line
    // Get the size of the word end so we can make some space for it
    // by not drawing the line over it
    var endW = ctx.measureText("END").width;
    
    // align to right
    ctx.textAlign = "right";
    ctx.textBaseline = "middle";  // draw over the line
    // using stroke text for small fonts is a cheaters whay of making it bold.
    ctx.strokeText("END",len-10,0); // draw it 10 pixels from the ens
    
    // put some text befor the start of the line
    ctx.fillStyle = "red";
    ctx.textAlign = "right"; // align text right so its draw away from the line
    ctx.fillText("->",-2,0); // draw text 2 pixels befor start
    
    ctx.textAlign = "left"; // align text right so its draw away from the line
    ctx.fillText("<-",len+2,0); // draw it 2 pixels past end pixels from the ens
    
    // Draw a -+- in the center of the line just because we can
    var centerW = ctx.measureText("-+-").width;
    ctx.textAlign = "center"; // align text to center
    ctx.fillText("-+-",len/2,0); // draw text at the center of the line
    
    // draw the line. As the world space is along the line
    // the line will just follow the x axies so it is easy to position
    // spaces and break the line up to make space for text
    ctx.beginPath();
    ctx.moveTo(0,0);
    ctx.lineTo(len/2-centerW/2-2,0);
    // space for center text
    ctx.moveTo(len/2+centerW/2+2,0);
    ctx.lineTo(len-endW - 12,0);
    // Space for the word END
    ctx.moveTo(len -8,0); // last part of the line
    ctx.lineTo(len ,0);
    ctx.stroke();  // draw it
    
    // restore the transformation.
    transformDefault(ctx);
}
.canC { width:500px;  height:500px;}
<canvas class="canC" id="canV" width=500 height=500></canvas>