Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HTML canvas drawing line perpendicular on another line

I'm using HTML canvas to draw Line as shown in the following figure, but this line has an edges on the two sides of it.

enter image description here

As shown in the image the two edges are not perpendicular to the major line.
I tried the following sollutions but it didn't succeed:
* rotate the edges lines, but the rotation will transform them from the original position
* find the angle of the major line and then draw the lines with respect to the line, but this solution was not easy to implement (most likely i implement it wrongly).

this is my code but it will always draw vertical edges:

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="200" height="200" style="border:1px solid #d3d3d3;">

<script>

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var x1 = 100;
var x2 = 150;
var y1 = 50;
var y2 = 120;

ctx.beginPath();
ctx.strokeStyle = "purple";  // Purple path
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);            
ctx.stroke();  // Draw it

ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x1,y1+10);
ctx.stroke();


ctx.restore();
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x1,(y1-10));
ctx.stroke();


ctx.beginPath();
ctx.moveTo(x2,y2);
ctx.lineTo(x2,y2+10);
ctx.stroke();


ctx.restore();
ctx.beginPath();
ctx.moveTo(x2,y2);
ctx.lineTo(x2,(y2-10));
ctx.stroke();


</script>
</body>
</html>

can anyone help me with rotating the two edges lines in order to become perpendicular to the major line. Thanks.

like image 373
Raed Khalaf Avatar asked Dec 05 '22 13:12

Raed Khalaf


2 Answers

Rotate any 2D vector 90deg

Perpendicular lines are simple to calculate if you remember the rotate 90deg rule for a 2D vector.

A vector {x,y} can be rotated 90 deg clockwise {-y,x} or anti clockwise {y,-x}. Swap x and y and negate the y for clockwise or x of anti clock wise

So for a line segment x1,y1 to x2,y2 convert to a vector, normalise that vector and rotate 90 deg as follows

function getPerpOfLine(x1,y1,x2,y2){ // the two points can not be the same
    var nx = x2 - x1;  // as vector
    var ny = y2 - y1;
    const len = Math.sqrt(nx * nx + ny * ny);  // length of line
    nx /= len;  // make one unit long
    ny /= len;  // which we call normalising a vector
    return [-ny, nx]; // return the normal  rotated 90 deg
}

Then say you want to draw a 10 pixel line at the ends of the line segment

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.append(canvas);
ctx.strokeStyle = "black";
ctx.lineJoin = ctx.lineCap = "round";
ctx.lineWidth = 3;

// the line segment
const x1 = 40, y1 = 40, x2 = 260, y2 = 110;
const endLen = 10; // length of end lines

var px = y1 - y2; // as vector at 90 deg to the line
var py = x2 - x1;
const len = endLen / Math.hypot(px, py);
px *= len;  // make leng 10 pixels
py *= len; 

// draw line the start cap and end cap.
ctx.beginPath();

ctx.lineTo(x1, y1);   // the line start
ctx.lineTo(x2, y2);
ctx.moveTo(x1 + px, y1 + py); // the start perp line
ctx.lineTo(x1 - px, y1 - py);
ctx.moveTo(x2 + px, y2 + py); // the end perp line
ctx.lineTo(x2 - px, y2 - py);
ctx.stroke();

Update

Simple solution to rendering content along a line, Uses the same 90deg rule.

There is an alternative method of rendering using the same vector rotation, but rather than set the perpendicular axis via vector multiplication, you set the y axis of the transform to 90deg from the x axis which is along the line. Set the origin to the start of the line and you simply render relative to the line.

setTransformToLine(x1, y1, x2, y2)

The following function will set the canvas transform to be along the line

// Set 2D context  current transform along the line x1,y1,x2,y2 and origin to
// start of line. y Axis is rotated clockwise 90 from the line.
// Returns the line length as that is most frequently required when
// using the method saving some time.
function setTransformToLine(x1, y1, x2, y2) {
  const vx = x2 - x1;   // get the line as vector
  const vy = y2 - y1;
  const len = Math.hypot(vx, vy); // For <= IE11 use Math.sqrt(vx * vx + vy * vy)
  const nx = vx / len; // Normalise the line vector. Making it one
  const ny = vy / len; // pixel long. This sets the scale

  // The transform is the normalised line vector for x axis, y at 90 deg 
  // and origin at line start
  ctx.setTransform(nx, ny, -ny, nx, x1, y1); // set transform

  return len;
}

How to use

This example show how to use the transform to do the same line but add annotated length as well.

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.append(canvas);
ctx.strokeStyle = "black";
ctx.lineJoin = ctx.lineCap = "round";
ctx.lineWidth = 3;
ctx.font = "16px arial";
ctx.textBaseline = "middle";
ctx.textAlign = "center";

const x1 = 40, y1 = 40, x2 = 260, y2 = 110;
const endLen = 10; 


function setTransformToLine(x1, y1, x2, y2) {
  const vx = x2 - x1; 
  const vy = y2 - y1;
  const len = Math.hypot(vx, vy); 
  const nx = vx / len; 
  const ny = vy / len;
  ctx.setTransform(nx, ny, -ny, nx, x1, y1);
  return len;
}

// Set the transform along the line. Keep the line length
// line len is need to get the x coord of the end of the line
const lineLen = setTransformToLine(x1, y1, x2, y2);

const lineLenStr = Math.round(lineLen) + "px";
const textWidth = ctx.measureText(lineLenStr).width;

const rlen = lineLen - textWidth - 16; // find the remaining line len after removing space for text

// Rendering is done in line local coordinates
// line is from (0,0) to (lineLen,0)

// Now draw the line the ends first and then along the line leaving gap for text
ctx.beginPath();
ctx.lineTo(0, -endLen);             // start perp line
ctx.lineTo(0,  endLen); 

ctx.moveTo(lineLen, -endLen);       // end of line is at lineLen
ctx.lineTo(lineLen,  endLen); 

ctx.moveTo(0,0);                    // line start segment
ctx.lineTo(rlen / 2, 0);

ctx.moveTo(lineLen - rlen / 2,0);   // line end segment
ctx.lineTo(lineLen, 0);

ctx.stroke(); // render it.

// now add text at the line center
ctx.fillText(lineLenStr, lineLen / 2, 0);

// To restore the transform to its default use identity matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
like image 52
Blindman67 Avatar answered Dec 08 '22 01:12

Blindman67


You need only calculate the slope of the original line, which is (y2 - y1)/(x2 - x1) and then use that to slope the new lines at the edges of the original. Here's a simple example:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var x1 = 100;
var x2 = 150;
var y1 = 50;
var y2 = 120;

ctx.beginPath();
ctx.strokeStyle = "purple"; // Purple path
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke(); // Draw it

var slope = (y2 - y1) / (x2 - x1);

ctx.beginPath();
ctx.lineTo(x1 + slope * 4, y1 - slope * 4);
ctx.lineTo(x1 - slope * 4, y1 + slope * 4);
ctx.stroke();

ctx.beginPath();
ctx.lineTo(x2 - slope * 4, y2 + slope * 4);
ctx.lineTo(x2 + slope * 4, y2 - slope * 4);
ctx.stroke();
<canvas id="myCanvas" width="200" height="200" style="border:1px solid #d3d3d3;">

Note that this is more of a mathematical theory question, thus the example just showcases the idea of how to do this. You should take a moment to read more about math and geometry to perfectly implement the logic behind this.

On a side note, your code has been simplified, as a few of the draw calls for the perpendicular lines were redundant and could have been merged.

like image 44
Angelos Chalaris Avatar answered Dec 08 '22 01:12

Angelos Chalaris