@joint/core
Version:
JavaScript diagramming library
357 lines (256 loc) • 10.2 kB
JavaScript
import { Point } from './point.mjs';
import { Rect } from './rect.mjs';
import { bearing } from './line.bearing.mjs';
import { squaredLength } from './line.squaredLength.mjs';
import { length } from './line.length.mjs';
import { types } from './types.mjs';
const {
max,
min
} = Math;
export const Line = function(p1, p2) {
if (!(this instanceof Line)) {
return new Line(p1, p2);
}
if (p1 instanceof Line) {
return new Line(p1.start, p1.end);
}
this.start = new Point(p1);
this.end = new Point(p2);
};
Line.prototype = {
type: types.Line,
// @returns the angle of incline of the line.
angle: function() {
var horizontalPoint = new Point(this.start.x + 1, this.start.y);
return this.start.angleBetween(this.end, horizontalPoint);
},
bbox: function() {
var left = min(this.start.x, this.end.x);
var top = min(this.start.y, this.end.y);
var right = max(this.start.x, this.end.x);
var bottom = max(this.start.y, this.end.y);
return new Rect(left, top, (right - left), (bottom - top));
},
// @return the bearing (cardinal direction) of the line. For example N, W, or SE.
// @returns {String} One of the following bearings : NE, E, SE, S, SW, W, NW, N.
bearing: function() {
return bearing(this.start, this.end);
},
clone: function() {
return new Line(this.start, this.end);
},
// @return {point} the closest point on the line to point `p`
closestPoint: function(p) {
return this.pointAt(this.closestPointNormalizedLength(p));
},
closestPointLength: function(p) {
return this.closestPointNormalizedLength(p) * this.length();
},
// @return {number} the normalized length of the closest point on the line to point `p`
closestPointNormalizedLength: function(p) {
var product = this.vector().dot((new Line(this.start, p)).vector());
var cpNormalizedLength = min(1, max(0, product / this.squaredLength()));
// cpNormalizedLength returns `NaN` if this line has zero length
// we can work with that - if `NaN`, return 0
if (cpNormalizedLength !== cpNormalizedLength) return 0; // condition evaluates to `true` if and only if cpNormalizedLength is `NaN`
// (`NaN` is the only value that is not equal to itself)
return cpNormalizedLength;
},
closestPointTangent: function(p) {
return this.tangentAt(this.closestPointNormalizedLength(p));
},
// Returns `true` if the point lies on the line.
containsPoint: function(p) {
var start = this.start;
var end = this.end;
if (start.cross(p, end) !== 0) return false;
// else: cross product of 0 indicates that this line and the vector to `p` are collinear
var length = this.length();
if ((new Line(start, p)).length() > length) return false;
if ((new Line(p, end)).length() > length) return false;
// else: `p` lies between start and end of the line
return true;
},
// Divides the line into two at requested `ratio` between 0 and 1.
divideAt: function(ratio) {
var dividerPoint = this.pointAt(ratio);
// return array with two lines
return [
new Line(this.start, dividerPoint),
new Line(dividerPoint, this.end)
];
},
// Divides the line into two at requested `length`.
divideAtLength: function(length) {
var dividerPoint = this.pointAtLength(length);
// return array with two new lines
return [
new Line(this.start, dividerPoint),
new Line(dividerPoint, this.end)
];
},
equals: function(l) {
return !!l &&
this.start.x === l.start.x &&
this.start.y === l.start.y &&
this.end.x === l.end.x &&
this.end.y === l.end.y;
},
// @return {point} Point where I'm intersecting a line.
// @return [point] Points where I'm intersecting a rectangle.
// @see Squeak Smalltalk, LineSegment>>intersectionWith:
intersect: function(shape, opt) {
if (shape && shape.intersectionWithLine) {
var intersection = shape.intersectionWithLine(this, opt);
// Backwards compatibility
if (intersection && (shape instanceof Line)) {
intersection = intersection[0];
}
return intersection;
}
return null;
},
intersectionWithLine: function(line) {
var pt1Dir = new Point(this.end.x - this.start.x, this.end.y - this.start.y);
var pt2Dir = new Point(line.end.x - line.start.x, line.end.y - line.start.y);
var det = (pt1Dir.x * pt2Dir.y) - (pt1Dir.y * pt2Dir.x);
var deltaPt = new Point(line.start.x - this.start.x, line.start.y - this.start.y);
var alpha = (deltaPt.x * pt2Dir.y) - (deltaPt.y * pt2Dir.x);
var beta = (deltaPt.x * pt1Dir.y) - (deltaPt.y * pt1Dir.x);
if (det === 0 || alpha * det < 0 || beta * det < 0) {
// No intersection found.
return null;
}
if (det > 0) {
if (alpha > det || beta > det) {
return null;
}
} else {
if (alpha < det || beta < det) {
return null;
}
}
return [new Point(
this.start.x + (alpha * pt1Dir.x / det),
this.start.y + (alpha * pt1Dir.y / det)
)];
},
isDifferentiable: function() {
return !this.start.equals(this.end);
},
// @return {double} length of the line
length: function() {
return length(this.start, this.end);
},
// @return {point} my midpoint
midpoint: function() {
return new Point(
(this.start.x + this.end.x) / 2,
(this.start.y + this.end.y) / 2
);
},
parallel: function(distance) {
const l = this.clone();
if (!this.isDifferentiable()) return l;
const { start, end } = l;
const eRef = start.clone().rotate(end, 270);
const sRef = end.clone().rotate(start, 90);
start.move(sRef, distance);
end.move(eRef, distance);
return l;
},
// @return {point} my point at 't' <0,1>
pointAt: function(t) {
var start = this.start;
var end = this.end;
if (t <= 0) return start.clone();
if (t >= 1) return end.clone();
return start.lerp(end, t);
},
pointAtLength: function(length) {
var start = this.start;
var end = this.end;
var fromStart = true;
if (length < 0) {
fromStart = false; // negative lengths mean start calculation from end point
length = -length; // absolute value
}
var lineLength = this.length();
if (length >= lineLength) return (fromStart ? end.clone() : start.clone());
return this.pointAt((fromStart ? (length) : (lineLength - length)) / lineLength);
},
// @return {number} the offset of the point `p` from the line. + if the point `p` is on the right side of the line, - if on the left and 0 if on the line.
pointOffset: function(p) {
// Find the sign of the determinant of vectors (start,end), where p is the query point.
p = new Point(p);
var start = this.start;
var end = this.end;
var determinant = ((end.x - start.x) * (p.y - start.y) - (end.y - start.y) * (p.x - start.x));
return determinant / this.length();
},
rotate: function(origin, angle) {
this.start.rotate(origin, angle);
this.end.rotate(origin, angle);
return this;
},
round: function(precision) {
this.start.round(precision);
this.end.round(precision);
return this;
},
scale: function(sx, sy, origin) {
this.start.scale(sx, sy, origin);
this.end.scale(sx, sy, origin);
return this;
},
// @return {number} scale the line so that it has the requested length
setLength: function(length) {
var currentLength = this.length();
if (!currentLength) return this;
var scaleFactor = length / currentLength;
return this.scale(scaleFactor, scaleFactor, this.start);
},
// @return {integer} length without sqrt
// @note for applications where the exact length is not necessary (e.g. compare only)
squaredLength: function() {
return squaredLength(this.start, this.end);
},
tangentAt: function(t) {
if (!this.isDifferentiable()) return null;
var start = this.start;
var end = this.end;
var tangentStart = this.pointAt(t); // constrains `t` between 0 and 1
var tangentLine = new Line(start, end);
tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y); // move so that tangent line starts at the point requested
return tangentLine;
},
tangentAtLength: function(length) {
if (!this.isDifferentiable()) return null;
var start = this.start;
var end = this.end;
var tangentStart = this.pointAtLength(length);
var tangentLine = new Line(start, end);
tangentLine.translate(tangentStart.x - start.x, tangentStart.y - start.y); // move so that tangent line starts at the point requested
return tangentLine;
},
toString: function() {
return this.start.toString() + ' ' + this.end.toString();
},
serialize: function() {
return this.start.serialize() + ' ' + this.end.serialize();
},
translate: function(tx, ty) {
this.start.translate(tx, ty);
this.end.translate(tx, ty);
return this;
},
// @return vector {point} of the line
vector: function() {
return new Point(this.end.x - this.start.x, this.end.y - this.start.y);
}
};
// For backwards compatibility:
Line.prototype.intersection = Line.prototype.intersect;
// For backwards compatibility:
export const line = Line;