UNPKG

@jakebeamish/penplotting

Version:

A JavaScript framework for making SVG files for penplotters.

144 lines (127 loc) 4.08 kB
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; } }