plotboilerplate
Version:
A simple javascript plotting boilerplate for 2d stuff.
1,223 lines (1,213 loc) • 593 kB
JavaScript
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 597:
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
AlloyFinger: () => (/* reexport */ AlloyFinger),
"default": () => (/* binding */ esm)
});
;// CONCATENATED MODULE: ./node_modules/alloyfinger-typescript/src/esm/alloy_finger.js
/* Port from AlloyFinger v0.1.15
* Original by dntzhang
* Typescript port by Ikaros Kappler
* Github: https://github.com/IkarosKappler/AlloyFinger-Typescript
*
* @date 2021-02-10 (Typescript port)
* @version 0.1.18
*/
;
/**
* Tiny math function to calculate the length of a vector in euclidean space.
*
* @param {XYCoords} v - The vector in {x,y} notation.
* @return {number} The length of the vector.
*/
const getLen = (v) => {
return Math.sqrt(v.x * v.x + v.y * v.y);
};
/**
* Tiny math function to calculate the dot product of two vectors.
*
* @param {XYCoords} v1 - The first vector in {x,y} notation.
* @param {XYCoords} v2 - The second vector in {x,y} notation.
* @return {number} The dot product of both vectors.
*/
const dot = (v1, v2) => {
return v1.x * v2.x + v1.y * v2.y;
};
/**
* Tiny math function to calculate the angle between two vectors.
*
* @param {XYCoords} v1 - The first vector in {x,y} notation.
* @param {XYCoords} v2 - The second vector in {x,y} notation.
* @return {number} The angle (in radians) between the two vectors.
*/
const getAngle = (v1, v2) => {
const mr = getLen(v1) * getLen(v2);
if (mr === 0)
return 0;
var r = dot(v1, v2) / mr;
if (r > 1)
r = 1;
return Math.acos(r);
};
/**
* Tiny math function to calculate the cross product of two vectors.
*
* @param {XYCoords} v1 - The first vector in {x,y} notation.
* @param {XYCoords} v2 - The second vector in {x,y} notation.
* @return {number} The cross product of both vectors.
*/
const cross = (v1, v2) => {
return v1.x * v2.y - v2.x * v1.y;
};
/**
* Tiny math function to calculate the rotate-angle (in degrees) for two vectors.
*
* @param {XYCoords} v1 - The first vector in {x,y} notation.
* @param {XYCoords} v2 - The second vector in {x,y} notation.
* @return {number} The rotate-angle in degrees for the two vectors.
*/
const getRotateAngle = (v1, v2) => {
var angle = getAngle(v1, v2);
if (cross(v1, v2) > 0) {
angle *= -1;
}
return angle * 180 / Math.PI;
};
/**
* A HandlerAdmin holds all the added event handlers for one kind of event type.
*/
class HandlerAdmin {
constructor(el) {
this.handlers = [];
this.el = el;
}
;
add(handler) {
this.handlers.push(handler);
}
;
del(handler) {
if (!handler)
this.handlers = [];
for (var i = this.handlers.length; i >= 0; i--) {
if (this.handlers[i] === handler) {
this.handlers.splice(i, 1);
}
}
}
;
dispatch(..._args) {
for (var i = 0, len = this.handlers.length; i < len; i++) {
const handler = this.handlers[i];
if (typeof handler === 'function') {
handler.apply(this.el, arguments);
}
}
}
;
} // END class HandlerAdmin
/**
* A wrapper for handler functions; converts the passed handler function into a HadlerAdmin instance..
*/
const wrapFunc = (el, handler) => {
const handlerAdmin = new HandlerAdmin(el);
handlerAdmin.add(handler);
return handlerAdmin;
};
/**
* @classdesc The AlloyFinger main class. Use this to add handler functions for
* touch events to any HTML- or SVG-Element.
**/
class AlloyFinger {
constructor(el, option) {
this.element = typeof el == 'string' ? document.querySelector(el) : el;
// Fancy stuff: change `this` from the start-, move-, end- and cancel-function.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
this.start = this.start.bind(this);
this.move = this.move.bind(this);
this.end = this.end.bind(this);
this.cancel = this.cancel.bind(this);
this.element.addEventListener("touchstart", this.start, false);
this.element.addEventListener("touchmove", this.move, false);
this.element.addEventListener("touchend", this.end, false);
this.element.addEventListener("touchcancel", this.cancel, false);
this.preV = { x: null, y: null };
this.pinchStartLen = null;
this.zoom = 1;
this.isDoubleTap = false;
const noop = () => { };
this.rotate = wrapFunc(this.element, option.rotate || noop);
this.touchStart = wrapFunc(this.element, option.touchStart || noop);
this.multipointStart = wrapFunc(this.element, option.multipointStart || noop);
this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop);
this.pinch = wrapFunc(this.element, option.pinch || noop);
this.swipe = wrapFunc(this.element, option.swipe || noop);
this.tap = wrapFunc(this.element, option.tap || noop);
this.doubleTap = wrapFunc(this.element, option.doubleTap || noop);
this.longTap = wrapFunc(this.element, option.longTap || noop);
this.singleTap = wrapFunc(this.element, option.singleTap || noop);
this.pressMove = wrapFunc(this.element, option.pressMove || noop);
this.twoFingerPressMove = wrapFunc(this.element, option.twoFingerPressMove || noop);
this.touchMove = wrapFunc(this.element, option.touchMove || noop);
this.touchEnd = wrapFunc(this.element, option.touchEnd || noop);
this.touchCancel = wrapFunc(this.element, option.touchCancel || noop);
this._cancelAllHandler = this.cancelAll.bind(this);
if (globalThis && typeof globalThis.addEventListener === "function") {
globalThis.addEventListener('scroll', this._cancelAllHandler);
}
this.delta = null;
this.last = null;
this.now = null;
this.tapTimeout = null;
this.singleTapTimeout = null;
this.longTapTimeout = null;
this.swipeTimeout = null;
this.x1 = this.x2 = this.y1 = this.y2 = null;
this.preTapPosition = { x: null, y: null };
}
;
start(evt) {
if (!evt.touches)
return;
const _self = this;
this.now = Date.now();
this.x1 = evt.touches[0].pageX;
this.y1 = evt.touches[0].pageY;
this.delta = this.now - (this.last || this.now);
this.touchStart.dispatch(evt, this.element);
if (this.preTapPosition.x !== null) {
this.isDoubleTap = (this.delta > 0 && this.delta <= 250 && Math.abs(this.preTapPosition.x - this.x1) < 30 && Math.abs(this.preTapPosition.y - this.y1) < 30);
if (this.isDoubleTap)
clearTimeout(this.singleTapTimeout);
}
this.preTapPosition.x = this.x1;
this.preTapPosition.y = this.y1;
this.last = this.now;
const preV = this.preV;
const len = evt.touches.length;
if (len > 1) {
this._cancelLongTap();
this._cancelSingleTap();
const v = { x: evt.touches[1].pageX - this.x1, y: evt.touches[1].pageY - this.y1 };
preV.x = v.x;
preV.y = v.y;
this.pinchStartLen = getLen(preV);
this.multipointStart.dispatch(evt, this.element);
}
this._preventTap = false;
this.longTapTimeout = setTimeout((() => {
_self.longTap.dispatch(evt, _self.element);
_self._preventTap = true;
}).bind(_self), 750);
}
;
move(event) {
if (!event.touches)
return;
const afEvent = event;
const preV = this.preV;
const len = event.touches.length;
const currentX = event.touches[0].pageX;
const currentY = event.touches[0].pageY;
this.isDoubleTap = false;
if (len > 1) {
const sCurrentX = afEvent.touches[1].pageX;
const sCurrentY = afEvent.touches[1].pageY;
const v = { x: afEvent.touches[1].pageX - currentX, y: afEvent.touches[1].pageY - currentY };
if (preV.x !== null) {
if (this.pinchStartLen > 0) {
afEvent.zoom = getLen(v) / this.pinchStartLen;
this.pinch.dispatch(afEvent, this.element);
}
afEvent.angle = getRotateAngle(v, preV);
this.rotate.dispatch(afEvent, this.element);
}
preV.x = v.x;
preV.y = v.y;
if (this.x2 !== null && this.sx2 !== null) {
afEvent.deltaX = (currentX - this.x2 + sCurrentX - this.sx2) / 2;
afEvent.deltaY = (currentY - this.y2 + sCurrentY - this.sy2) / 2;
}
else {
afEvent.deltaX = 0;
afEvent.deltaY = 0;
}
this.twoFingerPressMove.dispatch(afEvent, this.element);
this.sx2 = sCurrentX;
this.sy2 = sCurrentY;
}
else {
if (this.x2 !== null) {
afEvent.deltaX = currentX - this.x2;
afEvent.deltaY = currentY - this.y2;
//move事件中添加对当前触摸点到初始触摸点的判断,
//如果曾经大于过某个距离(比如10),就认为是移动到某个地方又移回来,应该不再触发tap事件才对。
//
// translation:
// Add the judgment of the current touch point to the initial touch point in the event,
// If it has been greater than a certain distance (such as 10), it is considered to be
// moved to a certain place and then moved back, and the tap event should no longer be triggered.
const movedX = Math.abs(this.x1 - this.x2);
const movedY = Math.abs(this.y1 - this.y2);
if (movedX > 10 || movedY > 10) {
this._preventTap = true;
}
}
else {
afEvent.deltaX = 0;
afEvent.deltaY = 0;
}
this.pressMove.dispatch(afEvent, this.element);
}
this.touchMove.dispatch(afEvent, this.element);
this._cancelLongTap();
this.x2 = currentX;
this.y2 = currentY;
if (len > 1) {
event.preventDefault();
}
}
; // END move
end(event) {
if (!event.changedTouches)
return;
const afEvent = event;
this._cancelLongTap();
const self = this;
if (afEvent.touches.length < 2) {
this.multipointEnd.dispatch(afEvent, this.element);
this.sx2 = this.sy2 = null;
}
//swipe
if ((this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
(this.y2 && Math.abs(this.y1 - this.y2) > 30)) {
afEvent.direction = this._swipeDirection(this.x1, this.x2, this.y1, this.y2);
this.swipeTimeout = setTimeout(function () {
self.swipe.dispatch(afEvent, self.element);
}, 0);
}
else {
this.tapTimeout = setTimeout(function () {
if (!self._preventTap) {
self.tap.dispatch(afEvent, self.element);
}
// trigger double tap immediately
if (self.isDoubleTap) {
self.doubleTap.dispatch(afEvent, self.element);
self.isDoubleTap = false;
}
}, 0);
if (!self.isDoubleTap) {
self.singleTapTimeout = setTimeout(function () {
self.singleTap.dispatch(afEvent, self.element);
}, 250);
}
}
this.touchEnd.dispatch(afEvent, this.element);
this.preV.x = 0;
this.preV.y = 0;
this.zoom = 1;
this.pinchStartLen = null;
this.x1 = this.x2 = this.y1 = this.y2 = null;
}
; // END end
cancelAll() {
this._preventTap = true;
clearTimeout(this.singleTapTimeout);
clearTimeout(this.tapTimeout);
clearTimeout(this.longTapTimeout);
clearTimeout(this.swipeTimeout);
}
;
cancel(evt) {
this.cancelAll();
this.touchCancel.dispatch(evt, this.element);
}
;
_cancelLongTap() {
clearTimeout(this.longTapTimeout);
}
;
_cancelSingleTap() {
clearTimeout(this.singleTapTimeout);
}
;
_swipeDirection(x1, x2, y1, y2) {
return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down');
}
;
on(evt, handler) {
if (this[evt]) {
// Force the generic parameter into it's expected candidate here ;)
const admin = this[evt];
admin.add(handler);
}
}
;
off(evt, handler) {
if (this[evt]) {
// Force the generic parameter into it's expected candidate here ;)
const admin = this[evt];
admin.del(handler);
}
}
;
destroy() {
if (this.singleTapTimeout) {
clearTimeout(this.singleTapTimeout);
}
if (this.tapTimeout) {
clearTimeout(this.tapTimeout);
}
if (this.longTapTimeout) {
clearTimeout(this.longTapTimeout);
}
if (this.swipeTimeout) {
clearTimeout(this.swipeTimeout);
}
this.element.removeEventListener("touchstart", this.start);
this.element.removeEventListener("touchmove", this.move);
this.element.removeEventListener("touchend", this.end);
this.element.removeEventListener("touchcancel", this.cancel);
this.rotate.del();
this.touchStart.del();
this.multipointStart.del();
this.multipointEnd.del();
this.pinch.del();
this.swipe.del();
this.tap.del();
this.doubleTap.del();
this.longTap.del();
this.singleTap.del();
this.pressMove.del();
this.twoFingerPressMove.del();
this.touchMove.del();
this.touchEnd.del();
this.touchCancel.del();
this.preV = this.pinchStartLen = this.zoom = this.isDoubleTap = this.delta = this.last = this.now = this.tapTimeout = this.singleTapTimeout = this.longTapTimeout = this.swipeTimeout = this.x1 = this.x2 = this.y1 = this.y2 = this.preTapPosition = this.rotate = this.touchStart = this.multipointStart = this.multipointEnd = this.pinch = this.swipe = this.tap = this.doubleTap = this.longTap = this.singleTap = this.pressMove = this.touchMove = this.touchEnd = this.touchCancel = this.twoFingerPressMove = null;
if (globalThis && typeof globalThis.removeEventListener === "function") {
globalThis.removeEventListener('scroll', this._cancelAllHandler);
}
}
; // END destroy
}
;
/* harmony default export */ const alloy_finger = ((/* unused pure expression or super */ null && (AlloyFinger)));
//# sourceMappingURL=alloy_finger.js.map
;// CONCATENATED MODULE: ./node_modules/alloyfinger-typescript/src/esm/index.js
/**
* TypeScript port by Ikaros Kappler.
*
* Original file from https://github.com/AlloyTeam/AlloyFinger
*
* @date 2021-02-10
*/
/* harmony default export */ const esm = (AlloyFinger);
//# sourceMappingURL=index.js.map
/***/ }),
/***/ 733:
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
/**
* @author Ikaros Kappler
* @date 2013-08-19
* @modified 2018-08-16 Added closure. Removed the 'IKRS' wrapper.
* @modified 2018-11-20 Added circular auto-adjustment.
* @modified 2018-11-25 Added the point constants to the BezierPath class itself.
* @modified 2018-11-28 Added the locateCurveByStartPoint() function.
* @modified 2018-12-04 Added the toSVGString() function.
* @modified 2019-03-23 Added JSDoc tags.
* @modified 2019-03-23 Changed the fuctions getPoint and getPointAt to match semantics in the Line class.
* @modified 2019-11-18 Fixed the clone function: adjustCircular attribute was not cloned.
* @modified 2019-12-02 Removed some excessive comments.
* @modified 2019-12-04 Fixed the missing obtainHandleLengths behavior in the adjustNeightbourControlPoint function.
* @modified 2020-02-06 Added function locateCurveByEndPoint( Vertex ).
* @modified 2020-02-11 Added 'return this' to the scale(Vertex,number) and to the translate(Vertex) function.
* @modified 2020-03-24 Ported this class from vanilla-JS to Typescript.
* @modified 2020-06-03 Made the private helper function _locateUIndex to a private function.
* @modified 2020-06-03 Added the getBounds() function.
* @modified 2020-07-14 Changed the moveCurvePoint(...,Vertex) to moveCurvePoint(...,XYCoords).
* @modified 2020-07-24 Added the getClosestT(Vertex) function.
* @modified 2020-12-29 Constructor is now private (no explicit use intended).
* @modified 2021-05-25 Added BezierPath.fromReducedList( Array<number> ).
* @modified 2022-01-31 Added `BezierPath.getEvenDistributionVertices(number)`.
* @modified 2022-02-02 Added the `destroy` method.
* @modified 2022-02-02 Cleared the `toSVGString` function (deprecated). Use `drawutilssvg` instead.
* @modified 2023-10-06 Adding the `BezierPath.toPathPoints()` method.
* @modified 2023-10-07 Adding the `BezierPath.fromCurve(CubicBezierCurve)` static function.
* @version 2.6.0
*
* @file BezierPath
* @public
**/
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.BezierPath = void 0;
var Bounds_1 = __webpack_require__(76);
var CubicBezierCurve_1 = __webpack_require__(973);
var UIDGenerator_1 = __webpack_require__(938);
var Vertex_1 = __webpack_require__(787);
/**
* @classdesc A BezierPath class.
*
* This was refactored from an older project.
*
* @requires Bounds
* @requires Vertex
* @requires CubicBezierCurve
* @requires XYCoords
* @requires SVGSerializable
* @requires UID
* @requires UIDGenerator
**/
var BezierPath = /** @class */ (function () {
/**
* The constructor.<br>
* <br>
* This constructor expects a sequence of path points and will approximate
* the location of control points by picking some between the points.<br>
* You should consider just constructing empty paths and then add more curves later using
* the addCurve() function.
*
* @constructor
* @name BezierPath
* @param {Vertex[]} pathPoints - An array of path vertices (no control points).
**/
function BezierPath() {
/**
* Required to generate proper CSS classes and other class related IDs.
**/
this.className = "BezierPath";
/** @constant {number} */
this.START_POINT = 0;
/** @constant {number} */
this.START_CONTROL_POINT = 1;
/** @constant {number} */
this.END_CONTROL_POINT = 2;
/** @constant {number} */
this.END_POINT = 3;
// pathPoints: Array<Vertex> | undefined | null) {
this.uid = UIDGenerator_1.UIDGenerator.next();
// if (!pathPoints) {
// pathPoints = [];
// }
this.totalArcLength = 0.0;
// Set this flag to true if you want the first point and
// last point of the path to be auto adjusted, too.
this.adjustCircular = false;
this.bezierCurves = [];
}
/**
* Add a cubic bezier curve to the end of this path.
*
* @method addCurve
* @param {CubicBezierCurve} curve - The curve to be added to the end of the path.
* @instance
* @memberof BezierPath
* @return {void}
**/
BezierPath.prototype.addCurve = function (curve) {
if (curve == null || typeof curve == "undefined")
throw "Cannot add null curve to bézier path.";
this.bezierCurves.push(curve);
if (this.bezierCurves.length > 1) {
curve.startPoint = this.bezierCurves[this.bezierCurves.length - 2].endPoint;
this.adjustSuccessorControlPoint(this.bezierCurves.length - 2, // curveIndex,
true, // obtainHandleLength,
true // updateArcLengths
);
}
else {
this.totalArcLength += curve.getLength();
}
};
/**
* Locate the curve with the given start point (function returns the index).
*
* @method locateCurveByStartPoint
* @param {Vertex} point - The (curve start-) point to look for.
* @instance
* @memberof BezierPath
* @return {number} The curve index or -1 if curve (start-) point not found
**/
BezierPath.prototype.locateCurveByStartPoint = function (point) {
// for( var i in this.bezierCurves ) {
for (var i = 0; i < this.bezierCurves.length; i++) {
if (this.bezierCurves[i].startPoint.equals(point))
return i;
}
return -1;
};
/**
* Locate the curve with the given end point (function returns the index).
*
* @method locateCurveByEndPoint
* @param {Vertex} point - The (curve end-) point to look for.
* @instance
* @memberof BezierPath
* @return {number} The curve index or -1 if curve (end-) point not found
**/
BezierPath.prototype.locateCurveByEndPoint = function (point) {
// for( var i in this.bezierCurves ) {
for (var i = 0; i < this.bezierCurves.length; i++) {
if (this.bezierCurves[i].endPoint.equals(point))
return i;
}
return -1;
};
/**
* Locate the curve with the given start point (function returns the index).
*
* @method locateCurveByStartControlPoint
* @param {Vertex} point - The (curve endt-) point to look for.
* @instance
* @memberof BezierPath
* @return {number} The curve index or -1 if curve (end-) point not found
**/
BezierPath.prototype.locateCurveByStartControlPoint = function (point) {
// for( var i in this.bezierCurves ) {
for (var i = 0; i < this.bezierCurves.length; i++) {
if (this.bezierCurves[i].startControlPoint.equals(point))
return i;
}
return -1;
};
// +---------------------------------------------------------------------------------
// | Locate the curve with the given end control point.
// |
// | @param point:Vertex The point to look for.
// | @return Number The index or -1 if not found.
// +-------------------------------
BezierPath.prototype.locateCurveByEndControlPoint = function (point) {
// for( var i in this.bezierCurves ) {
for (var i = 0; i < this.bezierCurves.length; i++) {
if (this.bezierCurves[i].endControlPoint.equals(point))
return i;
}
return -1;
};
/**
* Get the total length of this path.<br>
* <br>
* Note that the returned value comes from the curve buffer. Unregistered changes
* to the curve points will result in invalid path length values.
*
* @method getLength
* @instance
* @memberof BezierPath
* @return {number} The (buffered) length of the path.
**/
BezierPath.prototype.getLength = function () {
return this.totalArcLength;
};
/**
* This function is internally called whenever the curve or path configuration
* changed. It updates the attribute that stores the path length information.<br>
* <br>
* If you perform any unregistered changes to the curve points you should call
* this function afterwards to update the curve buffer. Not updating may
* result in unexpected behavior.
*
* @method updateArcLengths
* @instance
* @memberof BezierPath
* @return {void}
**/
BezierPath.prototype.updateArcLengths = function () {
this.totalArcLength = 0.0;
for (var i = 0; i < this.bezierCurves.length; i++) {
this.bezierCurves[i].updateArcLengths();
this.totalArcLength += this.bezierCurves[i].getLength();
}
};
/**
* Get the number of curves in this path.
*
* @method getCurveCount
* @instance
* @memberof BezierPath
* @return {number} The number of curves in this path.
**/
BezierPath.prototype.getCurveCount = function () {
return this.bezierCurves.length;
};
/**
* Get the cubic bezier curve at the given index.
*
* @method getCurveAt
* @param {number} index - The curve index from 0 to getCurveCount()-1.
* @instance
* @memberof BezierPath
* @return {CubicBezierCurve} The curve at the specified index.
**/
BezierPath.prototype.getCurveAt = function (curveIndex) {
return this.bezierCurves[curveIndex];
};
/**
* Move the whole bezier path by the given (x,y)-amount.
*
* @method translate
* @param {Vertex} amount - The amount to be added (amount.x and amount.y)
* to each vertex of the curve.
* @instance
* @memberof BezierPath
* @return {BezierPath} this for chaining
**/
BezierPath.prototype.translate = function (amount) {
for (var i = 0; i < this.bezierCurves.length; i++) {
var curve = this.bezierCurves[i];
curve.getStartPoint().add(amount);
curve.getStartControlPoint().add(amount);
curve.getEndControlPoint().add(amount);
}
// Don't forget to translate the last curve's last point
var curve = this.bezierCurves[this.bezierCurves.length - 1];
curve.getEndPoint().add(amount);
this.updateArcLengths();
return this;
};
/**
* Scale the whole bezier path by the given uniform factor.
*
* @method scale
* @param {Vertex} anchor - The scale origin to scale from.
* @param {number} scaleFactor - The scalar to be multiplied with.
* @instance
* @memberof BezierPath
* @return {BezierPath} this for chaining.
**/
BezierPath.prototype.scale = function (anchor, scaleFactor) {
return this.scaleXY({ x: scaleFactor, y: scaleFactor }, anchor);
};
/**
* Scale the whole bezier path by the given (x,y)-factors.
*
* @method scale
* @param {Vertex} anchor - The scale origin to scale from.
* @param {number} amount - The scalar to be multiplied with.
* @instance
* @memberof BezierPath
* @return {BezierPath} this for chaining.
**/
BezierPath.prototype.scaleXY = function (scaleFactors, anchor) {
for (var i = 0; i < this.bezierCurves.length; i++) {
var curve = this.bezierCurves[i];
curve.getStartPoint().scaleXY(scaleFactors, anchor);
curve.getStartControlPoint().scaleXY(scaleFactors, anchor);
curve.getEndControlPoint().scaleXY(scaleFactors, anchor);
// Do NOT scale the end point here!
// Don't forget that the curves are connected and on curve's end point
// the the successor's start point (same instance)!
}
// Finally move the last end point (was not scaled yet)
if (this.bezierCurves.length > 0 && !this.adjustCircular) {
this.bezierCurves[this.bezierCurves.length - 1].getEndPoint().scaleXY(scaleFactors, anchor);
}
this.updateArcLengths();
return this;
};
/**
* Rotate the whole bezier path around a point..
*
* @method rotate
* @param {Vertex} angle - The angle to rotate this path by.
* @param {Vertex} center - The rotation center.
* @instance
* @memberof BezierPath
* @return {void}
**/
BezierPath.prototype.rotate = function (angle, center) {
for (var i = 0; i < this.bezierCurves.length; i++) {
var curve = this.bezierCurves[i];
curve.getStartPoint().rotate(angle, center);
curve.getStartControlPoint().rotate(angle, center);
curve.getEndControlPoint().rotate(angle, center);
// Do NOT rotate the end point here!
// Don't forget that the curves are connected and on curve's end point
// the the successor's start point (same instance)!
}
// Finally move the last end point (was not scaled yet)
if (this.bezierCurves.length > 0 && !this.adjustCircular) {
this.bezierCurves[this.bezierCurves.length - 1].getEndPoint().rotate(angle, center);
}
};
/**
* Get the 't' position on this curve with the minimal distance to point p.
*
* @param {Vertex} p - The point to find the closest curve point for.
* @return {number} A value t with 0.0 <= t <= 1.0.
**/
BezierPath.prototype.getClosestT = function (p) {
// Find the spline to extract the value from
var minIndex = -1;
var minDist = 0.0;
var dist = 0.0;
var curveT = 0.0;
var uMin = 0.0;
var u = 0.0;
for (var i = 0; i < this.bezierCurves.length; i++) {
curveT = this.bezierCurves[i].getClosestT(p);
dist = this.bezierCurves[i].getPointAt(curveT).distance(p);
if (minIndex == -1 || dist < minDist) {
minIndex = i;
minDist = dist;
uMin = u + curveT * this.bezierCurves[i].getLength();
}
u += this.bezierCurves[i].getLength();
}
return Math.max(0.0, Math.min(1.0, uMin / this.totalArcLength));
};
/**
* Get the point on the bézier path at the given relative path location.
*
* @method getPoint
* @param {number} u - The relative path position: <pre>0 <= u <= this.getLength()</pre>
* @instance
* @memberof BezierPath
* @return {Vertex} The point at the relative path position.
**/
BezierPath.prototype.getPoint = function (u) {
if (u < 0 || u > this.totalArcLength) {
console.warn("[BezierPath.getPoint(u)] u is out of bounds: " + u + ".");
u = Math.min(this.totalArcLength, Math.max(u, 0));
}
// Find the spline to extract the value from
var i = 0;
var uTemp = 0.0;
while (i < this.bezierCurves.length && uTemp + this.bezierCurves[i].getLength() < u) {
uTemp += this.bezierCurves[i].getLength();
i++;
}
// if u == arcLength
// -> i is max
if (i >= this.bezierCurves.length)
return this.bezierCurves[this.bezierCurves.length - 1].getEndPoint().clone();
var bCurve = this.bezierCurves[i];
var relativeU = u - uTemp;
return bCurve.getPoint(relativeU);
};
/**
* Get the point on the bézier path at the given path fraction.
*
* @method getPointAt
* @param {number} t - The absolute path position: <pre>0.0 <= t <= 1.0</pre>
* @instance
* @memberof BezierPath
* @return {Vertex} The point at the absolute path position.
**/
BezierPath.prototype.getPointAt = function (t) {
return this.getPoint(t * this.totalArcLength);
};
/**
* Get the tangent of the bézier path at the given path fraction.<br>
* <br>
* Note that the returned vector is not normalized.
*
* @method getTangentAt
* @param {number} t - The absolute path position: <pre>0.0 <= t <= 1.0</pre>
* @instance
* @memberof BezierPath
* @return {Vertex} The tangent vector at the absolute path position.
**/
BezierPath.prototype.getTangentAt = function (t) {
return this.getTangent(t * this.totalArcLength);
};
/**
* Get the tangent of the bézier path at the given path location.<br>
* <br>
* Note that the returned vector is not normalized.
*
* @method getTangent
* @param {number} u - The relative path position: <pre>0 <= u <= getLength()</pre>
* @instance
* @memberof BezierPath
* @return {Vertex} The tangent vector at the relative path position.
**/
BezierPath.prototype.getTangent = function (u) {
if (u < 0 || u > this.totalArcLength) {
console.warn("[BezierPath.getTangent(u)] u is out of bounds: " + u + ".");
// return undefined;
u = Math.min(this.totalArcLength, Math.max(0, u));
}
// Find the spline to extract the value from
var i = 0;
var uTemp = 0.0;
while (i < this.bezierCurves.length && uTemp + this.bezierCurves[i].getLength() < u) {
uTemp += this.bezierCurves[i].getLength();
i++;
}
var bCurve = this.bezierCurves[i];
var relativeU = u - uTemp;
return bCurve.getTangent(relativeU);
};
/**
* Get the perpendicular of the bézier path at the given absolute path location (fraction).<br>
* <br>
* Note that the returned vector is not normalized.
*
* @method getPerpendicularAt
* @param {number} t - The absolute path position: <pre>0.0 <= t <= 1.0</pre>
* @instance
* @memberof BezierPath
* @return {Vertex} The perpendicluar vector at the absolute path position.
**/
BezierPath.prototype.getPerpendicularAt = function (t) {
return this.getPerpendicular(t * this.totalArcLength);
};
/**
* Get the perpendicular of the bézier path at the given relative path location.<br>
* <br>
* Note that the returned vector is not normalized.
*
* @method getPerpendicular
* @param {number} u - The relative path position: <pre>0 <= u <= getLength()</pre>
* @instance
* @memberof BezierPath
* @return {Vertex} The perpendicluar vector at the relative path position.
**/
BezierPath.prototype.getPerpendicular = function (u) {
if (u < 0 || u > this.totalArcLength) {
console.log("[BezierPath.getPerpendicular(u)] u is out of bounds: " + u + ".");
u = Math.min(this.totalArcLength, Math.max(0, u));
}
// Find the spline to extract the value from
var uResult = BezierPath._locateUIndex(this, u);
var bCurve = this.bezierCurves[uResult.i];
var relativeU = u - uResult.uPart;
return bCurve.getPerpendicular(relativeU);
};
/**
* This is a helper function to locate the curve index for a given
* absolute path position u.
*
* I decided to put this into privat scope as it is really specific. Maybe
* put this into a utils wrapper.
*
* Returns:
* - {number} i - the index of the containing curve.
* - {number} uPart - the absolute curve length sum (length from the beginning to u, should equal u itself).
* - {number} uBefore - the absolute curve length for all segments _before_ the matched curve (usually uBefore <= uPart).
**/
BezierPath._locateUIndex = function (path, u) {
var i = 0;
var uTemp = 0.0;
var uBefore = 0.0;
while (i < path.bezierCurves.length && uTemp + path.bezierCurves[i].getLength() < u) {
uTemp += path.bezierCurves[i].getLength();
if (i + 1 < path.bezierCurves.length)
uBefore += path.bezierCurves[i].getLength();
i++;
}
return { i: i, uPart: uTemp, uBefore: uBefore };
};
/**
* Get a specific sub path from this path. The start and end position are specified by
* ratio number in [0..1].
*
* 0.0 is at the beginning of the path.
* 1.0 is at the end of the path.
*
* Values below 0 or beyond 1 are cropped down to the [0..1] interval.
*
* startT > endT is allowed, the returned sub path will have inverse direction then.
*
* @method getSubPathAt
* @param {number} startT - The start position of the sub path.
* @param {number} endT - The end position of the sub path.
* @instance
* @memberof BezierPath
* @return {BezierPath} The desired sub path in the bounds [startT..endT].
**/
BezierPath.prototype.getSubPathAt = function (startT, endT) {
startT = Math.max(0, startT);
endT = Math.min(1.0, endT);
var startU = startT * this.totalArcLength;
var endU = endT * this.totalArcLength;
var uStartResult = BezierPath._locateUIndex(this, startU); // { i:int, uPart:float, uBefore:float }
var uEndResult = BezierPath._locateUIndex(this, endU); // { i:int, uPart:float, uBefore:float }
var firstT = (startU - uStartResult.uBefore) / this.bezierCurves[uStartResult.i].getLength();
if (uStartResult.i == uEndResult.i) {
// Subpath begins and ends in the same path segment (just get a simple sub curve from that path element).
var lastT = (endU - uEndResult.uBefore) / this.bezierCurves[uEndResult.i].getLength();
var firstCurve = this.bezierCurves[uStartResult.i].getSubCurveAt(firstT, lastT);
return BezierPath.fromArray([firstCurve]);
}
else {
var curves = [];
if (uStartResult.i > uEndResult.i) {
// Back to front direction
var firstCurve = this.bezierCurves[uStartResult.i].getSubCurveAt(firstT, 0.0);
curves.push(firstCurve);
for (var i = uStartResult.i - 1; i > uEndResult.i; i--) {
curves.push(this.bezierCurves[i].clone().reverse());
}
var lastT = (endU - uEndResult.uBefore) / this.bezierCurves[uEndResult.i].getLength();
curves.push(this.bezierCurves[uEndResult.i].getSubCurveAt(1.0, lastT));
}
else {
// Front to back direction
var firstCurve = this.bezierCurves[uStartResult.i].getSubCurveAt(firstT, 1.0);
curves.push(firstCurve);
for (var i = uStartResult.i + 1; i < uEndResult.i && i < this.bezierCurves.length; i++) {
curves.push(this.bezierCurves[i].clone());
}
var lastT = (endU - uEndResult.uBefore) / this.bezierCurves[uEndResult.i].getLength();
curves.push(this.bezierCurves[uEndResult.i].getSubCurveAt(0, lastT));
}
return BezierPath.fromArray(curves);
}
};
/**
* This function moves the addressed curve point (or control point) with
* keeping up the path's curve integrity.<br>
* <br>
* Thus is done by moving neighbour- and control- points as needed.
*
* @method moveCurvePoint
* @param {number} curveIndex - The curve index to move a point from.
* @param {number} pointID - One of the curve's four point IDs (START_POINT,
* START_CONTROL_POINT, END_CONTRO_POINT or END_POINT).
* @param {XYCoords} moveAmount - The amount to move the addressed vertex by.
* @instance
* @memberof BezierPath
* @return {void}
**/
BezierPath.prototype.moveCurvePoint = function (curveIndex, pointID, moveAmount) {
var bCurve = this.getCurveAt(curveIndex);
bCurve.moveCurvePoint(pointID, moveAmount, true, // move control point, too
true // updateArcLengths
);
// If inner point and NOT control point
// --> move neightbour
if (pointID == this.START_POINT && (curveIndex > 0 || this.adjustCircular)) {
// Set predecessor's control point!
var predecessor = this.getCurveAt(curveIndex - 1 < 0 ? this.bezierCurves.length + (curveIndex - 1) : curveIndex - 1);
predecessor.moveCurvePoint(this.END_CONTROL_POINT, moveAmount, true, // move control point, too
false // updateArcLengths
);
}
else if (pointID == this.END_POINT && (curveIndex + 1 < this.bezierCurves.length || this.adjustCircular)) {
// Set successcor
var successor = this.getCurveAt((curveIndex + 1) % this.bezierCurves.length);
successor.moveCurvePoint(this.START_CONTROL_POINT, moveAmount, true, // move control point, too
false // updateArcLengths
);
}
else if (pointID == this.START_CONTROL_POINT && curveIndex > 0) {
this.adjustPredecessorControlPoint(curveIndex, true, // obtain handle length?
false // update arc lengths
);
}
else if (pointID == this.END_CONTROL_POINT && curveIndex + 1 < this.getCurveCount()) {
this.adjustSuccessorControlPoint(curveIndex, true, // obtain handle length?
false // update arc lengths
);
}
// Don't forget to update the arc lengths!
// Note: this can be optimized as only two curves have changed their lengths!
this.updateArcLengths();
};
/**
* This helper function adjusts the given point's predecessor's control point.
*
* @method adjustPredecessorControlPoint
* @param {number} curveIndex - The curve index to move a point from.
* @param {boolean} obtainHandleLength - Moves the point with keeping the original handle length.
* @param {boolean} updateArcLength - The amount to move the addressed vertex by.
* @instance
* @private
* @memberof BezierPath
* @return {void}
**/
BezierPath.prototype.adjustPredecessorControlPoint = function (curveIndex, obtainHandleLength, updateArcLengths) {
if (!this.adjustCircular && curveIndex <= 0)
return; // false;
var mainCurve = this.getCurveAt(curveIndex);
var neighbourCurve = this.getCurveAt(curveIndex - 1 < 0 ? this.getCurveCount() + (curveIndex - 1) : curveIndex - 1);
BezierPath.adjustNeighbourControlPoint(mainCurve, neighbourCurve, mainCurve.getStartPoint(), // the reference point
mainCurve.getStartControlPoint(), // the dragged control point
neighbourCurve.getEndPoint(), // the neighbour's point
neighbourCurve.getEndControlPoint(), // the neighbour's control point to adjust
obtainHandleLength, updateArcLengths);
};
/**
* This helper function adjusts the given point's successor's control point.
*
* @method adjustSuccessorControlPoint
* @param {number} curveIndex - The curve index to move a point from.
* @param {boolean} obtainHandleLength - Moves the point with keeping the original handle length.
* @param {boolean} updateArcLength - The amount to move the addressed vertex by.
* @instance
* @private
* @memberof BezierPath
* @return {void}
**/
BezierPath.prototype.adjustSuccessorControlPoint = function (curveIndex, obtainHandleLength, updateArcLengths) {
if (!this.adjustCircular && curveIndex + 1 > this.getCurveCount())
return; // false;
var mainCurve = this.getCurveAt(curveIndex);
var neighbourCurve = this.getCurveAt((curveIndex + 1) % this.getCurveCount());
/* return */ BezierPath.adjustNeighbourControlPoint(mainCurve, neighbourCurve, mainCurve.getEndPoint(), // the reference point
mainCurve.getEndControlPoint(), // the dragged control point
neighbourCurve.getStartPoint(), // the neighbour's point
neighbourCurve.getStartControlPoint(), // the neighbour's control point to adjust
obtainHandleLength, updateArcLengths);
};
/**
* This helper function adjusts the given point's successor's control point.
*
* @method adjustNeighbourControlPoint
* @param {CubicBezierCurve} mainCurve
* @param {CubicBezierCurve} neighbourCurve
* @param {Vertex} mainPoint
* @param {Vertex} mainControlPoint
* @param {Vertex} neighbourPoint
* @param {Vertex} neighbourControlPoint
* @param {boolean} obtainHandleLengths
* @param {boolean} updateArcLengths
* @instance
* @private
* @memberof BezierPath
* @return {void}
**/
BezierPath.adjustNeighbourControlPoint = function (_mainCurve, // TODO: remove param
neighbourCurve, mainPoint, mainControlPoint, neighbourPoint, neighbourControlPoint, obtainHandleLengths, _updateArcLengths // TODO: remove param
) {
// Calculate start handle length
var mainHandleBounds = new Vertex_1.Vertex(mainControlPoint.x - mainPoint.x, mainControlPoint.y - mainPoint.y);
var neighbourHandleBounds = new Vertex_1.Vertex(neighbourControlPoint.x - neighbourPoint.x, neighbourControlPoint.y - neighbourPoint.y);
var mainHandleLength = Math.sqrt(Math.pow(mainHandleBounds.x, 2) + Math.pow(mainHandleBounds.y, 2));
var neighbourHandleLength = Math.sqrt(Math.pow(neighbourHandleBounds.x, 2) + Math.pow(neighbourHandleBounds.y, 2));
if (mainHandleLength <= 0.1)
return; // no secure length available for division? What about zoom? Use EPSILON?
// Just invert the main handle (keep length or not?
if (obtainHandleLengths) {
neighbourControlPoint.set(neighbourPoint.x - mainHandleBounds.x * (neighbourHandleLength / mainHandleLength), neighbourPoint.y - mainHandleBounds.y * (neighbourHandleLength / mainHandleLength));
}
else {
neighbourControlPoint.set(neighbourPoint.x - mainHandleBounds.x, neighbourPoint.y - mainHandleBounds.y);
}
neighbourCurve.updateArcLengths();
};
/**
* Get the bounds of this Bézier path.
*
* Note the the curves' underlyung segment buffers are used to determine the bounds. The more
* elements the segment buffers have, the more precise the returned bounds will be.
*
* @return {Bounds} The bounds of this Bézier path.
**/
BezierPath.prototype.getBounds = function () {
var min = new Vertex_1.Vertex(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY);
var max = new Vertex_1.Vertex(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
var b;
for (var i = 0; i < this.bezierCurves.length; i++) {
b = this.bezierCurves[i].getBounds();
min.x = Math.min(min.x, b.min.x);
min.y = Math.min(min.y, b.min.y);
max.x = Math.max(max.x, b.max.x);
max.y = Math.max(max.y, b.max.y);
}
return new Bounds_1.Bounds(min, max);
};
/**
* Get n 'equally' distributed vertices along this Bézier path.
*
* As the changing curvature of the B slines makes prediction of distances difficult, the
* returned vertices' distances are only relatively equal:
* - the distance grows where curvature is large.
* - the distance shrinks where curvature is small.
*
* Only the distance mean of all consecutive is 1/n-th of the total arc length.
*
* Usually this approximation is good enough for most use cases.
*
* @param {number} pointCount - (must be at least 2) The number of desired points (start and end point included).
* @return {Array<Vertex>}
*/
BezierPath.prototype.getEvenDistributionVertices = function (pointCount) {
if (pointCount < 2) {
throw new Error("pointCount must be larger than one; is " + pointCount + ".");
}
var result = [];
if (this.bezierCurves.length === 0) {
return result;
}
// Fetch and add the start point from the source polygon
var polygonPoint = new Vertex_1.Vertex(this.bezierCurves[0].startPoint);
result.push(polygonPoint);
// if (this.bezierCurves.length === 1) {
// return result;
// }
var perimeter = this.totalArcLength;
var stepSize = perimeter / (pointCount - 1);
var n = this.bezierCurves.length;
var curveIndex = 0;
var segmentLength = this.bezierCurves[0].arcLength;
var curSegmentU = stepSize;
var i = 1;
while (i < pointCount && curveIndex < n) {
// Check if next eq point is inside this segment
if (curSegmentU < segmentLength) {
var newPoint = this.bezierCurves[curveIndex].getPoint(curSegmentU);
result.push(newPoint);
curSegmentU += stepSize;
i++;
}
else {
curveIndex++;
curSegmentU = curSegmentU - segmentLength;
segmentLength = curveIndex < n ? this.bezierCurves[curveIndex].arcLength : 0;
}
}
result.push(new Vertex_1.Vertex(this.bezierCurves[n - 1].endPoint));
return result;
};
/**
* Clone this BezierPath (deep clone).
*
* @method clone
* @instance
* @memberof BezierPath
* @return {BezierPath}
**/
BezierPath.prototype.clone = function () {
var path = new BezierPath(); // undefined);
for (var i = 0; i < this.bezierCurves.length; i++) {
path.bezierCurves.push(this.bezierCurves[i].clone());
// Connect splines
if (i > 0)
path.bezierCurves[i - 1].endPoint = path.bezierCurves[i].startPoint;
}
path.updateArcLengths();
path.adjustCircular = this.adjustCircular;
return path;
};
/**
* Compare this and the passed Bézier path.
*
* @method equals
* @param {BezierPath} path - The pass to compare with.
* @instance
* @memberof BezierPath
* @return {boolean}
**/
BezierPath.prototype.equals = function (path) {
if (!path)
return false;
// Check if path contains the credentials
if (!path.bezierCurves)
return false;
if (typeof path.bezierCurves.length == "undefined")
return false;
if (path.bezierCurves.length != this.bezierCurves.length)
return false;
for (var i = 0; i < this.bezierCurves.length; i++) {
if (!this.bezierCurves[i].equals(path.bezierCurves[i]))
return false;
}
return