Q:

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

I need to annotate a line by placing text at the start and end. I also need to insert the text without the line being visible where the text is.
Could someone show me how to add text to lines for the 2D canvas context.

A:

Annotating lines with setTransform
Annotating a line is best done with the CanvasRenderingContext2D.setTransform()  so that you align the world space (your text) to the screen space (where your line is drawn). Once you have that then all drawing coordinates will be as if the line follows the top of the canvas.
The start of the line is at (0,0) and the end of the line is at the x coordinate equal to the length of the line. You can draw above, ontop and, below the line with the CanvasRenderingContext2D.textBaseline property. You can draw text align to the ends and centers with the CanvasRenderingContext2D.textAlign property.
To have gaps in the line use the CanvasRenderingContext2D.measureText function to get the text width and draw the line segments around the text.
The code sample has the function transformToLine(ctx, x1, y1, x2, y2) which sets the current canvas transform aligned to the line, then returns the length of the line so that it can be used to add your text.
I have also provided some code showing how to render on, in , before, after, over and under the line. There are also three utility functions that help draw text in the line.
The transformToLine(ctx, x1, y1, x2, y2) is a very handy function and has many uses, not only for drawing text along a line. Personally I find it so useful I add it to the canvas context rather than as a global function.

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>

