plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
703 lines • 24.9 kB
JavaScript
"use strict";
/**
* @author Ikaros Kappler
* @date 2012-10-17
* @modified 2018-04-03 Refactored the code of october 2012 into a new class.
* @modified 2018-04-28 Added some documentation.
* @modified 2018-08-16 Added the set() function.
* @modified 2018-08-26 Added VertexAttr.
* @modified 2018-10-31 Extended the constructor by object{x,y}.
* @modified 2018-11-19 Extended the set(number,number) function to set(Vertex).
* @modified 2018-11-28 Added 'this' to the VertexAttr constructor.
* @modified 2018-12-05 Added the sub(...) function. Changed the signature of the add() function! add(Vertex) and add(number,number) are now possible.
* @modified 2018-12-21 (It's winter solstice) Added the inv()-function.
* @modified 2019-01-30 Added the setX(Number) and setY(Number) functions.
* @modified 2019-02-19 Added the difference(Vertex) function.
* @modified 2019-03-20 Added JSDoc tags.
* @modified 2019-04-24 Added the randomVertex(ViewPort) function.
* @modified 2019-11-07 Added toSVGString(object) function.
* @modified 2019-11-18 Added the rotate(number,Vertex) function.
* @modified 2019-11-21 Fixed a bug in the rotate(...) function (elements were moved).
* @modified 2020-03-06 Added functions invX() and invY().
* @modified 2020-03-23 Ported to Typescript from JS.
* @modified 2020-05-26 Added functions addX(number) and addY(number).
* @modified 2020-10-30 Changed the warnings in `sub(...)` and `add(...)` into real errors.
* @modified 2021-03-01 Changed the second param `center` in the `rotate` function from Vertex to XYCoords.
* @modified 2021-12-01 Changed the type of param of `scale` to XYCoords.
* @modified 2021-12-01 Added function `scaleXY` for non uniform scaling.
* @modified 2021-12-17 Added the functions `lerp` and `lerpAbs` for linear interpolations.
* @modified 2022-01-31 Added `Vertex.utils.arrayToJSON`.
* @modified 2022-02-02 Added the `destroy` method.
* @modified 2022-02-02 Cleared the `Vertex.toSVGString` function (deprecated). Use `drawutilssvg` instead.
* @modified 2022-11-28 Added the `subXY`, `subX` and `subY` methods to the `Vertex` class.
* @modified 2023-09-29 Downgraded types for the `Vertex.utils.buildArrowHead` function (replacing Vertex params by more generic XYCoords type).
* @modified 2023-09-29 Added the `Vertex.abs()` method as it seems useful.
* @modified 2024-03-08 Added the optional `precision` param to the `toString` method.
* @modified 2024-12-17 Outsourced the euclidean distance calculation of `Vertex.distance` to `geomutils.dist4`.
* @modified 2025-03-24 Making the second parameter `center` of the `Vertex.rotate` method optional.
* @modified 2025-04-13 Adding the `Vertex.move(amount: XYCoords)` method (does the same as `add`, added by naming convention).
* @modified 2025-05-07 Class `Vertex` is now implementing interface `IBounded` (to meet convention).
* @version 2.11.0
*
* @file Vertex
* @public
**/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Vertex = void 0;
var VertexAttr_1 = require("./VertexAttr");
var UIDGenerator_1 = require("./UIDGenerator");
var VertexListeners_1 = require("./VertexListeners");
var geomutils_1 = require("./geomutils");
var Bounds_1 = require("./Bounds");
/**
* @classdesc A vertex is a pair of two numbers.<br>
* <br>
* It is used to identify a 2-dimensional point on the x-y-plane.
*
* @requires IVertexAttr
* @requires SVGSerializable
* @requires UID
* @requires UIDGenerator
* @requires VertexAttr
* @requires VertexListeners
* @requires XYCoords
*
*/
var Vertex = /** @class */ (function () {
/**
* The constructor for the vertex class.
*
* @constructor
* @name Vertex
* @param {number} x - The x-coordinate of the new vertex.
* @param {number} y - The y-coordinate of the new vertex.
**/
function Vertex(x, y) {
/**
* Required to generate proper CSS classes and other class related IDs.
**/
this.className = "Vertex";
this.uid = UIDGenerator_1.UIDGenerator.next();
if (typeof x == "undefined") {
this.x = 0;
this.y = 0;
}
else if (typeof x == "number" && typeof y == "number") {
this.x = x;
this.y = y;
}
else {
var tuple = x;
if (typeof tuple.x == "number" && typeof tuple.y == "number") {
this.x = tuple.x;
this.y = tuple.y;
}
else {
if (typeof x == "number")
this.x = x;
else if (typeof x == "undefined")
this.x = 0;
else
this.x = NaN;
if (typeof y == "number")
this.y = y;
else if (typeof y == "undefined")
this.y = 0;
else
this.y = NaN;
}
}
this.attr = new VertexAttr_1.VertexAttr();
this.listeners = new VertexListeners_1.VertexListeners(this);
}
/**
* Set the x- and y- component of this vertex.
*
* @method set
* @param {number} x - The new x-component.
* @param {number} y - The new y-component.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.set = function (x, y) {
if (typeof x == "number" && typeof y == "number") {
this.x = x;
this.y = y;
}
else {
var tuple = x;
if (typeof tuple.x == "number" && typeof tuple.y == "number") {
this.x = tuple.x;
this.y = tuple.y;
}
else {
if (typeof x == "number")
this.x = x;
else if (typeof x == "undefined")
this.x = 0;
else
this.x = NaN;
if (typeof y == "number")
this.y = y;
else if (typeof y == "undefined")
this.y = 0;
else
this.y = NaN;
}
}
return this;
};
/**
* Set the x-component of this vertex.
*
* @method setX
* @param {number} x - The new x-component.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.setX = function (x) {
this.x = x;
return this;
};
/**
* Set the y-component of this vertex.
*
* @method setY
* @param {number} y - The new y-component.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.setY = function (y) {
this.y = y;
return this;
};
/**
* Set the x-component if this vertex to the inverse of its value.
*
* @method invX
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.invX = function () {
this.x = -this.x;
return this;
};
/**
* Set the y-component if this vertex to the inverse of its value.
*
* @method invY
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.invY = function () {
this.y = -this.y;
return this;
};
/**
* Add the passed amount to x- and y- component of this vertex.<br>
* <br>
* This function works with add( {number}, {number} ) and
* add( {Vertex} ), as well.
*
* @method add
* @param {(number|Vertex)} x - The amount to add to x (or a vertex itself).
* @param {number=} y - The amount to add to y.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.add = function (x, y) {
if (typeof x == "number" && typeof y == "number") {
this.x += x;
this.y += y;
}
else {
var tuple = x;
if (typeof tuple.x == "number" && typeof tuple.y == "number") {
this.x += tuple.x;
this.y += tuple.y;
}
else {
if (typeof x == "number")
this.x += x;
else
throw "Cannot add ".concat(typeof x, " to numeric x component!");
if (typeof y == "number")
this.y += y;
else
throw "Cannot add ".concat(typeof y, " to numeric y component!");
}
}
return this;
};
/**
* Move this point by the given amount.
*
* This method just calls `add(amount).
*
*
* @method move
* @param {Vertex} amount - The amount to move this vertex.
* @return {Vertex} this - For chaining.
* @instance
* @memberof Vertex
*/
Vertex.prototype.move = function (amount) {
return this.add(amount);
};
/**
* Add the passed amounts to the x- and y- components of this vertex.
*
* @method addXY
* @param {number} x - The amount to add to x.
* @param {number} y - The amount to add to y.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.addXY = function (amountX, amountY) {
this.x += amountX;
this.y += amountY;
return this;
};
/**
* Add the passed amounts to the x-component of this vertex.
*
* @method addX
* @param {number} x - The amount to add to x.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.addX = function (amountX) {
this.x += amountX;
return this;
};
/**
* Add the passed amounts to the y-component of this vertex.
*
* @method addY
* @param {number} y - The amount to add to y.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.addY = function (amountY) {
this.y += amountY;
return this;
};
/**
* Substract the passed amount from x- and y- component of this vertex.<br>
* <br>
* This function works with sub( {number}, {number} ) and
* sub( {Vertex} ), as well.
*
* @method sub
* @param {(number|Vertex)} x - The amount to substract from x (or a vertex itself).
* @param {number=} y - The amount to substract from y.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.sub = function (x, y) {
if (typeof x == "number" && typeof y == "number") {
this.x -= x;
this.y -= y;
}
else {
var tuple = x;
if (typeof tuple.x == "number" && typeof tuple.y == "number") {
this.x -= tuple.x;
this.y -= tuple.y;
}
else {
if (typeof x == "number")
this.x -= x;
else
throw "Cannot add ".concat(typeof x, " to numeric x component!");
if (typeof y == "number")
this.y -= y;
else
throw "Cannot add ".concat(typeof y, " to numeric y component!");
}
}
return this;
};
/**
* Substract the passed amounts from the x- and y- components of this vertex.
*
* @method subXY
* @param {number} x - The amount to substract from x.
* @param {number} y - The amount to substract from y.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.subXY = function (amountX, amountY) {
this.x -= amountX;
this.y -= amountY;
return this;
};
/**
* Substract the passed amounts from the x-component of this vertex.
*
* @method addX
* @param {number} x - The amount to substract from x.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.subX = function (amountX) {
this.x -= amountX;
return this;
};
/**
* Substract the passed amounts from the y-component of this vertex.
*
* @method subY
* @param {number} y - The amount to substract from y.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.subY = function (amountY) {
this.y -= amountY;
return this;
};
/**
* Check if this vertex equals the passed one.
* <br>
* This function uses an internal epsilon as tolerance.
*
* @method equals
* @param {Vertex} vertex - The vertex to compare this with.
* @return {boolean}
* @instance
* @memberof Vertex
**/
Vertex.prototype.equals = function (vertex) {
var eqX = Math.abs(this.x - vertex.x) < Vertex.EPSILON;
var eqY = Math.abs(this.y - vertex.y) < Vertex.EPSILON;
var result = eqX && eqY;
return result;
};
/**
* Create a copy of this vertex.
*
* @method clone
* @return {Vertex} A new vertex, an exact copy of this.
* @instance
* @memberof Vertex
**/
Vertex.prototype.clone = function () {
return new Vertex(this.x, this.y);
};
/**
* Get the distance to the passed point (in euclidean metric)
*
* @method distance
* @param {XYCoords} vert - The vertex to measure the distance to.
* @return {number}
* @instance
* @memberof Vertex
**/
Vertex.prototype.distance = function (vert) {
// return Math.sqrt(Math.pow(vert.x - this.x, 2) + Math.pow(vert.y - this.y, 2));
return geomutils_1.geomutils.dist4(this.x, this.y, vert.x, vert.y);
};
/**
* Get the angle of this point (relative to (0,0) or to the given other origin point).
*
* @method angle
* @param {XYCoords} origin - The vertex to measure the angle from.
* @return {number}
* @instance
* @memberof Vertex
**/
Vertex.prototype.angle = function (origin) {
var a = typeof origin === "undefined"
? Math.PI / 2 - Math.atan2(this.x, this.y)
: Math.PI / 2 - Math.atan2(origin.x - this.x, origin.y - this.y);
// Map to positive value
return a < 0 ? Math.PI * 2 + a : a;
};
/**
* Get the difference to the passed point.<br>
* <br>
* The difference is (vert.x-this.x, vert.y-this.y).
*
* @method difference
* @param {Vertex} vert - The vertex to measure the x-y-difference to.
* @return {Vertex} A new vertex.
* @instance
* @memberof Vertex
**/
Vertex.prototype.difference = function (vert) {
return new Vertex(vert.x - this.x, vert.y - this.y);
};
/**
* This is a vector-like behavior and 'scales' this vertex
* towards/from a given center by one uniform scale factor.
*
* @method scale
* @param {number} factor - The factor to 'scale' this vertex; 1.0 means no change.
* @param {XYCoords=} center - The origin of scaling; default is (0,0).
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.scale = function (factor, center) {
return this.scaleXY({ x: factor, y: factor }, center);
};
/**
* Perform a linear interpolation towards the given target vertex.
* The amount value `t` is relative, `t=0.0` means no change, `t=1.0`
* means this point will be moved to the exact target position.
*
* `t=0.5` will move this point to the middle of the connecting
* linear segment.
*
* @param {XYCoords} target - The target position to lerp this vertex to.
* @param {number} t - The relative amount, usually in [0..1], but other values will work, too.
* @returns
*/
Vertex.prototype.lerp = function (target, t) {
var diff = this.difference(target);
// return new Vertex(this.x + diff.x * t, this.y + diff.y * t);
this.x += diff.x * t;
this.y += diff.y * t;
return this;
};
/**
* Perform a linear interpolation towards the given target vertex (absolute variant).
* The amount value `t` is absolute, which means the lerp amount is a direct distance
* value. This point will have move the amount of the passed distance `u`.
*
* @param {XYCoords} target - The target position to lerp this vertex to.
* @param {number} t - The absolute move amount to use to lerping.
* @returns
*/
Vertex.prototype.lerpAbs = function (target, u) {
var dist = this.distance(target);
var diff = this.difference(target);
var step = { x: diff.x / dist, y: diff.y / dist };
// return new Vertex(this.x + step.x * u, this.y + step.y * u);
this.x += step.x * u;
this.y += step.y * u;
return this;
};
/**
* This is a vector-like behavior and 'scales' this vertex
* towards/from a given center by two independent x- and y- scale factors.
*
* @method scale
* @param {number} factor - The factor to 'scale' this vertex; 1.0 means no change.
* @param {XYCoords=} center - The origin of scaling; default is (0,0).
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.scaleXY = function (factors, center) {
if (!center || typeof center === "undefined") {
center = { x: 0, y: 0 };
}
this.x = center.x + (this.x - center.x) * factors.x;
this.y = center.y + (this.y - center.y) * factors.y;
return this;
};
/**
* This is a vector-like behavior and 'rotates' this vertex
* around given center.
*
* @method rotate
* @param {number} angle - The angle to 'rotate' this vertex; 0.0 means no change.
* @param {XYCoords=} center - The center of rotation; default is (0,0).
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.rotate = function (angle, center) {
if (!center || typeof center === "undefined") {
center = { x: 0, y: 0 };
}
this.sub(center);
angle += Math.atan2(this.y, this.x);
var len = this.distance(Vertex.ZERO); // {x:0,y:0});
this.x = len * Math.cos(angle);
this.y = len * Math.sin(angle);
this.add(center);
return this;
};
/**
* Multiply both components of this vertex with the given scalar.<br>
* <br>
* Note: as in<br>
* https://threejs.org/docs/#api/math/Vector2.multiplyScalar
*
* @method multiplyScalar
* @param {number} scalar - The scale factor; 1.0 means no change.
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.multiplyScalar = function (scalar) {
this.x *= scalar;
this.y *= scalar;
return this;
};
/**
* Round the two components x and y of this vertex.
*
* @method round
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.round = function () {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
};
/**
* Change this vertex (x,y) to its inverse (-x,-y).
*
* @method inv
* @return {Vertex} this
* @instance
* @memberof Vertex
**/
Vertex.prototype.inv = function () {
this.x = -this.x;
this.y = -this.y;
return this;
};
/**
* Set both coordinates of this vertex to their absolute value (abs(x), abs(y)).
*
* @method abs
* @return {Vertex} this
* @instance
* @memberof Vertex
*/
Vertex.prototype.abs = function () {
this.x = Math.abs(this.x);
this.y = Math.abs(this.y);
return this;
};
//--- BEGIN --- Implement interface `IBounded`
/**
* Get the bounding box (bounds) of this Vertex.
* This is just by convention – any vertex is just a position without any useful with or height (0).
*
* @method getBounds
* @instance
* @memberof Vertex
* @return {Bounds} The rectangular bounds of this Vertex (width and height are zero).
**/
Vertex.prototype.getBounds = function () {
// return Bounds.computeFromVertices([this.a, this.b, this.c]);
return Bounds_1.Bounds.computeFromVertices([this]);
};
//--- END --- Implement interface `IBounded`
/**
* Get a string representation of this vertex.
*
* @method toString
* @return {string} The string representation of this vertex.
* @instance
* @memberof Vertex
**/
Vertex.prototype.toString = function (precision) {
if (typeof precision === "undefined") {
return "(" + this.x + "," + this.y + ")";
}
else {
return "(" + this.x.toFixed(precision) + "," + this.y.toFixed(precision) + ")";
}
};
/**
* This function should invalidate any installed listeners and invalidate this object.
* After calling this function the object might not hold valid data any more and
* should not be used.
*/
Vertex.prototype.destroy = function () {
this.listeners.removeAllListeners();
this.isDestroyed = true;
};
/**
* Create a new random vertex inside the given viewport.
*
* @param {ViewPort} viewPort - A {min:Vertex, max:Vertex} viewport specifying the bounds.
* @return A new vertex with a random position.
**/
Vertex.randomVertex = function (viewPort) {
return new Vertex(viewPort.min.x + Math.random() * (viewPort.max.x - viewPort.min.x), viewPort.min.y + Math.random() * (viewPort.max.y - viewPort.min.y));
};
Vertex.ZERO = new Vertex(0, 0);
/**
* An epsilon for comparison
*
* @private
* @readonly
**/
Vertex.EPSILON = 1.0e-6;
Vertex.utils = {
/**
* Generate a four-point arrow head, starting at the vector end minus the
* arrow head length.
*
* The first vertex in the returned array is guaranteed to be the located
* at the vector line end minus the arrow head length.
*
*
* Due to performance all params are required.
*
* The params scaleX and scaleY are required for the case that the scaling is not uniform (x and y
* scaling different). Arrow heads should not look distored on non-uniform scaling.
*
* If unsure use 1.0 for scaleX and scaleY (=no distortion).
* For headlen use 8, it's a good arrow head size.
*
* Example:
* buildArrowHead( new Vertex(0,0), new Vertex(50,100), 8, 1.0, 1.0 )
*
* @param {XYCoords} zA - The start vertex of the vector to calculate the arrow head for.
* @param {XYCoords} zB - The end vertex of the vector.
* @param {number} headlen - The length of the arrow head (along the vector direction. A good value is 12).
* @param {number} scaleX - The horizontal scaling during draw.
* @param {number} scaleY - the vertical scaling during draw.
**/
// @DEPRECATED: use Vector.utils.buildArrowHead instead!!!
buildArrowHead: function (zA, zB, headlen, scaleX, scaleY) {
console.warn("[DEPRECATION] Vertex.utils.buildArrowHead is deprecated. Please use Vector.utils.buildArrowHead instead.");
var angle = Math.atan2((zB.y - zA.y) * scaleY, (zB.x - zA.x) * scaleX);
var vertices = [];
vertices.push(new Vertex(zB.x * scaleX - headlen * Math.cos(angle), zB.y * scaleY - headlen * Math.sin(angle)));
vertices.push(new Vertex(zB.x * scaleX - headlen * 1.35 * Math.cos(angle - Math.PI / 8), zB.y * scaleY - headlen * 1.35 * Math.sin(angle - Math.PI / 8)));
vertices.push(new Vertex(zB.x * scaleX, zB.y * scaleY));
vertices.push(new Vertex(zB.x * scaleX - headlen * 1.35 * Math.cos(angle + Math.PI / 8), zB.y * scaleY - headlen * 1.35 * Math.sin(angle + Math.PI / 8)));
return vertices;
},
/**
* Convert the given vertices (array) to a JSON string.
*
* @param {number?} precision - (optional) The numeric precision to be used (number of precision digits).
* @returns {string}
*/
arrayToJSON: function (vertices, precision) {
return JSON.stringify(vertices.map(function (vert) {
return typeof precision === undefined
? { x: vert.x, y: vert.y }
: { x: Number(vert.x.toFixed(precision)), y: Number(vert.y.toFixed(precision)) };
}));
}
};
return Vertex;
}());
exports.Vertex = Vertex;
//# sourceMappingURL=Vertex.js.map