import { Vector } from "./Vector.js";
import { Plot } from "./Plot.js";
/** Class representing a line. */
export class Line {
/**
* Create a line between two [Vectors]{@link Vector}.
* @param {Vector} a - The startpoint.
* @param {Vector} b - The endpoint.
* @param {string} [options.stroke = "black"]
* @param {number} [options.strokeWidth = 0.1]
*/
constructor(a, b, { stroke, strokeWidth } = {}) {
this.a = a;
this.b = b;
this.x1 = a.x;
this.y1 = a.y;
this.x2 = b.x;
this.y2 = b.y;
const plot = Plot.getContext();
this.stroke = stroke !== undefined ? stroke : plot?.stroke || "black";
this.strokeWidth = strokeWidth !== undefined ? strokeWidth : plot?.strokeWidth || 0.1;
}
/**
* Create a Line from an array of numbers.
* @param {Array<number>} array - [x1, y1, x2, y2].
* @returns {Line}
*/
static fromArray(array) {
return new Line(
new Vector(array[0], array[1]),
new Vector(array[2], array[3]),
);
}
/**
* Get the length.
* @returns {number} The distance from the startpoint to the endpoint.
* Uses the {@link Vector#distance} method.
*/
length() {
return this.a.distance(this.b);
}
/**
* Check if this line is the same as another line.
* @param {Line} line - The target line to compare to.
* @returns {boolean} True if startpoints and endpoints are identical or in reverse.
*/
isDuplicate(line) {
return (
(this.a.equals(line.a) && this.b.equals(line.b)) ||
(this.a.equals(line.b) && this.b.equals(line.a))
);
}
/**
* Check if this line lies directly on top of a longer line.
* @param {Line} line - The target line to check against.
* @returns {boolean} - True if this line's startpoint and endpoint are both
* on the line being checked against, otherwise False.
*/
isContainedBy(line) {
return this.a.isOnLine(line) && this.b.isOnLine(line);
}
/**
* Check if two lines intersect.
* @param {Line} line1
* @param {Line} line2
* @returns {(boolean|Vector)} - False if lines don't intersect, otherwise returns the intersection point as a {@link Vector}.
*/
static lineIntersection(line1, line2) {
const x1 = line1.a.x;
const y1 = line1.a.y;
const x2 = line1.b.x;
const y2 = line1.b.y;
const x3 = line2.a.x;
const y3 = line2.a.y;
const x4 = line2.b.x;
const y4 = line2.b.y;
const denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
// Check if lines are parallel
if (denominator === 0) {
return false;
}
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
// Check if intersection point lies within the line segments
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
const x = x1 + ua * (x2 - x1);
const y = y1 + ua * (y2 - y1);
return new Vector(x, y);
} else {
return false;
}
}
/**
* Check if this line intersects another shape.
* Currently only {@link Line} is supported.
* @param {object} - The target.
* @returns {(boolean|Array)} - False if lines don't intersect, otherwise returns the intersection point [x,y]
*/
intersects(target) {
if (target instanceof Line) {
return Line.lineIntersection(this, target);
} else {
throw new TypeError("Target shape must be a Line.");
}
}
toArray() {
return [this.a.x, this.a.y, this.b.x, this.b.y];
}
/**
*
* @returns {SVGElement}
*/
toSVGElement() {
let units = "";
let element = document.createElementNS(
"http://www.w3.org/2000/svg",
"line",
);
element.setAttribute("x1", `${this.a.x}${units}`);
element.setAttribute("y1", `${this.a.y}${units}`);
element.setAttribute("x2", `${this.b.x}${units}`);
element.setAttribute("y2", `${this.b.y}${units}`);
element.setAttribute("stroke", this.stroke);
element.setAttribute("stroke-width", `${this.strokeWidth}${units}`);
return element;
}
}