Make a line of the canvas with a dot at the end

I am trying to make the end of the canvas to a point (like an arrow, except that the sides should not go beyond the line width ... see the following picture for an example of how the end of the line should look).

See the following figure for an example of what the end of a line should look like.

I've experimented with line caps, but the only caps available are "round" or "square" ( http://www.w3schools.com/tags/canvas_linecap.asp ).

The following script is the line I am trying to give at the end.

http://jsfiddle.net/699ktkv8/

The code is also below:

<body>

<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

<script>

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 200, 100, 200, 20);
ctx.lineWidth=10
ctx.stroke();

</script> 

</body>

      

0


source to share


2 answers


Unfortunately, there is no such option in canvas. You will have to manually calculate the angle of the line result (which means you have to implement Bezier math).

Then use the line width to draw the cap based on that corner.

Step 1 - find direction

Lets you make it more complex by using one end that is not 90 ° up or to the side:

Step 1

var ctx = document.querySelector("canvas").getContext("2d");

// draw as normal
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=10
ctx.stroke();

// get two points from the end
var pt1 = order3(20, 20, 20, 100, 170, 70, 200, 20, 0.98);
var pt2 = order3(20, 20, 20, 100, 170, 70, 200, 20, 1);

// show direction
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.moveTo(pt1.x, pt1.y);
ctx.lineTo(pt1.x + (pt2.x - pt1.x) * 10, pt1.y + (pt2.y - pt1.y) * 10);
ctx.stroke();

//B(t) = (1-t)^3 * z0 + 3t (1-t)^2 * c0 + 3 t^2 (1-t) * c1 + t^3 * z1 for 0 <=t <= 1
function order3(z0x, z0y, c0x, c0y, c1x, c1y, z1x, z1y, t) {

  var tm1 = 1 - t,        // (1 - t)
    tm12 = tm1 * tm1,     // (1 - t) ^ 2
    tm13 = tm12 * tm1,    // (1 - t) ^ 3
    t2 = t * t,           // t ^ 2
    t3 = t2 * t,          // t ^ 3
    tmm3 = t * 3 * tm12,  // 3 x t * (1 - t) ^ 2
    tmm23 = t2 * 3 * tm1, // t ^ 2 * 3 * (1 - t)
    x, y;

  x = (tm13 * z0x + tmm3 * c0x + tmm23 * c1x + t3 * z1x + 0.5) | 0;
  y = (tm13 * z0y + tmm3 * c0y + tmm23 * c1y + t3 * z1y + 0.5) | 0;

  return {
    x: x,
    y: y
  }
}
      

<canvas width=220 height=100 />
      

Run codeHide result


Refresh or as label points, you can calculate it from control points (my bad one, I forgot about it completely - thanks markE). This is probably a better approach for most cases than using the " t

" approach .

I'll include it here for completeness:

// calculate the ending angle from the two last nodes (cp2 and end point)
var dx = pt2.x - cp2.x;   // assumes points and control points as objects
var dy = pt2.y - cp2.y;
var angle = Math.atan2(dy, dx);

      

End of update

Step 2 - find the angle and distance



We need to calculate the actual angle so that we can use this for the base of the arrow:

// get angle
var diffX = pt1.x - pt2.x;   // see update comment above
var diffY = pt1.y - pt2.y;
var angle = Math.atan2(diffY, diffX);
var tangent = Math.atan2(diffX, -diffY);

      

step 2

(shown slightly offset for purpose)

Step 3 - painting cap

We now have enough information to draw the cap on the line:

step 3

var ctx = document.querySelector("canvas").getContext("2d");

// draw as normal
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=40
ctx.stroke();

// get two points from the end
var pt1 = order3(20, 20, 20, 100, 170, 70, 200, 20, 0.98);
var pt2 = order3(20, 20, 20, 100, 170, 70, 200, 20, 1);
var diffX = pt1.x - pt2.x;
var diffY = pt1.y - pt2.y;
var angle = Math.atan2(diffY, diffX);
var tangent = Math.atan2(diffX, -diffY);
var lw = ctx.lineWidth * 0.5 - 0.5;

// draw cap
ctx.beginPath();
ctx.moveTo(pt2.x + lw * Math.cos(tangent), pt2.y + lw * Math.sin(tangent));
ctx.lineTo(pt2.x - lw * Math.cos(tangent), pt2.y - lw * Math.sin(tangent));
ctx.lineTo(pt1.x - lw * Math.cos(angle), pt1.y - lw * Math.sin(angle));

ctx.fill();

// due to inaccuracies, you may have to mask tiny gaps 
ctx.lineWidth = 1;
ctx.stroke();

//B(t) = (1-t)^3 * z0 + 3t (1-t)^2 * c0 + 3 t^2 (1-t) * c1 + t^3 * z1 for 0 <=t <= 1
function order3(z0x, z0y, c0x, c0y, c1x, c1y, z1x, z1y, t) {

  var tm1 = 1 - t,        // (1 - t)
    tm12 = tm1 * tm1,     // (1 - t) ^ 2
    tm13 = tm12 * tm1,    // (1 - t) ^ 3
    t2 = t * t,           // t ^ 2
    t3 = t2 * t,          // t ^ 3
    tmm3 = t * 3 * tm12,  // 3 x t * (1 - t) ^ 2
    tmm23 = t2 * 3 * tm1, // t ^ 2 * 3 * (1 - t)
    x, y;

  x = (tm13 * z0x + tmm3 * c0x + tmm23 * c1x + t3 * z1x + 0.5) | 0;
  y = (tm13 * z0y + tmm3 * c0y + tmm23 * c1y + t3 * z1y + 0.5) | 0;

  return {
    x: x,
    y: y
  }
}
      

<canvas width=240 height=100 />
      

Run codeHide result


The actual angle at the end depends on which point closer to the end you choose (not the actual end point, i.e. t=1

). You may need to calculate the total line length and use that as a basis for t

.

You may also encounter situations where the angle is not complete and small gaps appear.

You can either mask these gaps by applying a stroke, or offset the cap slightly based on the previously calculated angle / direction (use linear interpolation as in step 1, only negative t

), or the only other way to get accurate information is to manually calculate the line walls and so on, such as treating it as a polygon and filling it as a separate object.

+4


source


@KenFrystenberg answered your question well.

Here's an interesting math note to make calculations easier.

The end angle (and start angle) of a cubic Bezier curve can be calculated directly from the control points:

// define 4 cubic Bezier control points
var cp0={x:20,y:20};
var cp1={x:20,y:100};
var cp2={x:170,y:70};
var cp3={x:200,y:20};

// calculate the ending angle from cp2 & cp3
var dx=cp3.x-cp2.x;
var dy=cp3.y-cp2.y;
var angle=Math.atan2(dy,dx);

      



Here's some sample code and demo of this math note:

enter image description here

var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;

var cp0={x:20,y:20};
var cp1={x:20,y:100};
var cp2={x:170,y:70};
var cp3={x:200,y:20};

ctx.lineWidth=10;

ctx.beginPath();
ctx.moveTo(20, 20);
ctx.bezierCurveTo(20, 100, 170, 70, 200, 20);
ctx.lineWidth=20
ctx.stroke();

ctx.beginPath();
ctx.arc(200,20,3,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle='red';
ctx.fill();

var dx=cp3.x-cp2.x;
var dy=cp3.y-cp2.y;
var angle=Math.atan2(dy,dx);

var x=cp3.x+15*Math.cos(angle);
var y=cp3.y+15*Math.sin(angle);


ctx.beginPath();
ctx.moveTo(cp3.x,cp3.y);
ctx.lineTo(x,y);
ctx.lineWidth=1;
ctx.strokeStyle='red';
ctx.stroke();
      

body{ background-color: ivory; }
#canvas{border:1px solid red;}
      

<canvas id="canvas" width=300 height=300></canvas>
      

Run codeHide result


+1


source







All Articles