# Vector

[custom](..) ⟩ [class]() ⟩ Vector &#x20;

{% hint style="success" %}
support vector operations：u + v, u - v, k \* v ...

```javascript
u + v: u.plus(v)
u - v: u.minus(v)
k * v: v.times(k)
au + bv + cw: linearCombination([a,b,c], [u,v,w])
```

:point\_right: [..](.. "mention")
{% endhint %}

{% tabs %}
{% tab title="💾 程式" %}
* replit ⟩ [Vector (v1.1)](https://replit.com/@pegasusroe/Vector-v11#js/ext/Vector.js) ,  require -> [number+ext](../../../js/val/prim/num/number+ext "mention")

```javascript
// 2023.01.22 - 21:04 (/) refactor vec(x,y) -> vec(...args)
// 2023.01.21 - 13:18 (+) .clampInRect()
// 2023.01.15 - 19:56 (+) linearCombination()
// 2023.01.15 - 08:50 (?) refactor, first recorded.
// ------------------------------------------------------

const {sin, cos, sqrt} = Math;

// ⭐️ import 
// ------------------------------------------------------
import {  } from './Number+ext.js';    // 👔 Number + ext


// ⭐️ Vector
// ------------------------------------------------------
// - vec(x, y), vec(v)
// - polar(r, theta)
// - linearCombination(scalars, vectors)
// ------------------------------------------------------
// 🔸 .x
// 🔸 .y
// 🔸 .coords         : [x, y]
// 🔸 .inverse        : -v            (additive inverse)
// 🔹 .plus(...args)  : (v), (x, y)
// 🔹 .minus(...args) : (v), (x, y)
// 🔹 .times(k)       : u * k         (scalar product)
// ------------------------------------------------------
// 🔸 .length
// 🔹 .distanceTo(...args)    : (v), (x, y)
// ------------------------------------------------------
// 🔹 .toString()
// ------------------------------------------------------
// 
class Vector {

    // 🟧 Vector.zero
    static get zero() { return new Vector(0, 0) }

    // init
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    // 🔸 .coords
    get coords() { return [this.x, this.y] }

    // -----------
    //     -v
    // -----------

    // 🔸 .inverse
    get inverse() { return new Vector(-this.x, -this.y) }
    
    // -------------
    //     u + v
    // -------------

    // 🔹 u.plus(v) or u.plus(x, y)
    plus(...args) {

        // treat as u + v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x + v.x, this.y + v.y)
        }

        // treat as u + (x, y)
        if (args.length === 2) return this.plus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.plus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     u - v
    // -------------

    // 🔹 u.minus(v) or u.minus(x, y)
    minus(...args) { 
        // treat as u - v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x - v.x, this.y - v.y);
        }

        // treat as u - (x, y)
        if (args.length === 2) return this.minus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.minus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     k * v
    // -------------

    // 🔹 .times(k)
    times(k) { return new Vector(this.x * k, this.y * k) }

    // ------------------------
    //     distance related
    // ------------------------

    // 🔸 .length
    get length() { 
        const {x, y} = this;
        return sqrt(x*x + y*y); 
    }

    // 🔹 .distanceTo(v) or .distanceTo(x, y)
    distanceTo(...args) {

        // u.distanceTo(v)
        if (args.length === 1) {
            const v = args[0];
            return this.minus(v).length;
        }

        // u.distanceTo(x, y)
        if (args.length === 2) {
            return this.distanceTo(vec(...args));
        }

        // throw error otherwise
        throw new Error(`Vector.distanceTo() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // 🔹 .isEqualTo(v)
    isEqualTo(v, {threshold = 100 * Number.EPSILON}={}) {
        return areEqualNumbers(0, this.distanceTo(v), {threshold});
    }

    // ---------------
    //     rect
    // ---------------

    clampInRect(rect) {
        const {x: x1, y: y1} = rect.origin;
        const {x: x2, y: y2} = rect.bottomRight;
        return vec(this.x.clamp(x1, x2), this.y.clamp(y1, y2)); // 👔 Number + ext
    }

    // ---------------
    //     debug
    // ---------------

    // 🔹 .toString()
    toString() { return `(${this.x}, ${this.y})` }

    // ⭐️ custom type name
    // ❗️ note: typeof vec(1,2) === 'object' 
    get [Symbol.toStringTag]() { return 'Vector' }
}

// ----------------------------------------
// ⭐️ convenience factory functions
// ----------------------------------------

// vec(x, y), vec(v)        // 👔 Vector (v1.1)
function vec(...args) { 
    const len = args.length;
    // treat as vec(x, y)
    if (len === 2) return new Vector(...args);
    // treat as vec(v)
    if (len === 1) {
        const {x, y} = args[0];
        return new Vector(x, y)
    };
    // throw error otherwise
    throw new Error(`vec(): expecting 1 or 2 arguments, but got ${len}!`);
}

// polar(r, theta)
function polar(r, theta) { 
    return vec(r * cos(theta), r * sin(theta)) 
}

// linear combination: a1*v1 + a2*v2 + ... + an*vn
function linearCombination(scalars, vectors) {
    return vectors
        .map((v, i) => v.times(scalars[i] || 0))
        .reduce((sum, vec) => sum.plus(vec), Vector.zero);
}

// --------------
// ⭐️ helpers
// --------------

// ⭐️ areEqualNumbers(x, y {threshold})
// - Number.EPSILON = 2^(-52) ≈ 2.2 * 10^(-16)
function areEqualNumbers(x, y, {threshold = Number.EPSILON}={}) { 
    return Math.abs(x - y) < threshold; 
}

// 📤 export
// -----------------------------
export { vec, polar, Vector, linearCombination };            // export ES module
// module.exports = { vec, polar, Vector, linearCombination };
```
{% endtab %}

{% tab title="⬇️ 應用" %}
* [size](size "mention")
* [rect](rect "mention") - origin, size, width, height ...
* [+boxes](../../../browser/dom/type/element/+boxes "mention")
* [+ext](../../../browser/canvas/+ext "mention") - use `vec()`.
* [+ext](../../../browser/event/type/mouse/+ext "mention")
* [coordinates](../../../browser/concepts/coordinates "mention")
* [elem.position](../../../browser/dom/type/element/+ext/elem.position "mention") - ([border](../../../browser/dom/type/element/+boxes/border "mention")) position relative to some [coordinates](../../../browser/concepts/coordinates "mention") system.
* [curve](../../../browser/canvas/path/curve "mention")
* [arcto](../../../browser/canvas/path/arcto "mention")
{% endtab %}

{% tab title="👥 相關" %}
* [canvas](../../../browser/canvas "mention")
{% endtab %}

{% tab title="💈範例" %}
* [koch-snowflake](../../../browser/canvas/state/matrix/koch-snowflake "mention")
* [square-and-circle](../../../browser/canvas/examples/square-and-circle "mention")
{% endtab %}
{% endtabs %}

## History

* replit ⟩ [vector](https://replit.com/@pegasusroe/vector#js/Vector.js) (with canvas example)

{% tabs %}
{% tab title="idx" %}
```
0: (?) first recorded, refactor.
1: (+) linearCombination()
2: (+) .clampInRect()
```
{% endtab %}

{% tab title="0" %}
```javascript
// 2023.01.15 - 08:50 (?) first recorded
// ------------------------------------------------------

const {sin, cos} = Math;

// ⭐️ Vector
// ------------------------------------------------------
// 🔸 .x
// 🔸 .y
// 🔸 .coords         : [x, y]
// 🔸 .inverse        : -v            (additive inverse)
// 🔹 .add(v)         : u + v
// 🔹 .plus(x, y)     : u + (x, y)
// 🔹 .minus(v)       : u - v
// 🔹 .subtract(x, y) : u - (x, y)
// 🔹 .times(k)       : u * k         (scalar product)
// ------------------------------------------------------
// 🔹 .toString()
//
class Vector {

    // init
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    // 🔸 .coords
    get coords() { return [this.x, this.y] }
    
    // -------------
    //     u + v
    // -------------

    // 🔹 u.add(v)
    add(v) { return new Vector(this.x + v.x, this.y + v.y) }

    // 🔹 u.plus(x, y)
    plus(x, y) { return this.add(new Vector(x, y)) }

    // -----------
    //     -v
    // -----------

    // 🔸 .inverse
    get inverse() { return new Vector(-this.x, -this.y) }

    // -------------
    //     u - v
    // -------------

    // 🔹 u.minus(v)
    subtract(v) { return new Vector(this.x - v.x, this.y - v.y) }

    // 🔹 u.subtract(x, y)
    minus(x, y) { return this.subtract(new Vector(x, y)) }

    // -------------
    //     k * v
    // -------------

    // 🔹 .times(k)
    times(k) { return new Vector(this.x * k, this.y * k) }

    // ---------------
    //     helpers
    // ---------------

    // 🔹 .toString()
    toString() { return `(${this.x}, ${this.y})` }
}

// ----------------------------------------
// ⭐️ convenience factory functions
// ----------------------------------------

function vec(x, y) { return new Vector(x, y) }

function polar(r, theta) { 
    return vec(r * cos(theta), r * sin(theta)) 
}

// export
export {
    vec, polar, Vector,
};
```
{% endtab %}

{% tab title="1" %}
```javascript
// 2023.01.15 - 19:56 (+) linearCombination()
// 2023.01.15 - 08:50 (?) refactor, first recorded.
// ------------------------------------------------------

const {sin, cos, sqrt} = Math;

// ⭐️ Vector
// ------------------------------------------------------
// - vec(x, y)
// - polar(r, theta)
// - linearCombination(scalars, vectors)
// ------------------------------------------------------
// 🔸 .x
// 🔸 .y
// 🔸 .coords         : [x, y]
// 🔸 .inverse        : -v            (additive inverse)
// 🔹 .plus(...args)  : (v), (x, y)
// 🔹 .minus(...args) : (v), (x, y)
// 🔹 .times(k)       : u * k         (scalar product)
// ------------------------------------------------------
// 🔸 .length
// 🔹 .distanceTo(...args)    : (v), (x, y)
// ------------------------------------------------------
// 🔹 .toString()
// ------------------------------------------------------
// 
class Vector {

    // 🟧 Vector.zero
    static get zero() { return new Vector(0, 0) }

    // init
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    // 🔸 .coords
    get coords() { return [this.x, this.y] }

    // -----------
    //     -v
    // -----------

    // 🔸 .inverse
    get inverse() { return new Vector(-this.x, -this.y) }
    
    // -------------
    //     u + v
    // -------------

    // 🔹 u.plus(v) or u.plus(x, y)
    plus(...args) {

        // treat as u + v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x + v.x, this.y + v.y)
        }

        // treat as u + (x, y)
        if (args.length === 2) return this.plus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.plus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     u - v
    // -------------

    // 🔹 u.minus(v) or u.minus(x, y)
    minus(...args) { 
        // treat as u - v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x - v.x, this.y - v.y);
        }

        // treat as u - (x, y)
        if (args.length === 2) return this.minus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.minus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     k * v
    // -------------

    // 🔹 .times(k)
    times(k) { return new Vector(this.x * k, this.y * k) }

    // ------------------------
    //     distance related
    // ------------------------

    // 🔸 .length
    get length() { 
        const {x, y} = this;
        return sqrt(x*x + y*y); 
    }

    // 🔹 .distanceTo(v) or .distanceTo(x, y)
    distanceTo(...args) {

        // u.distanceTo(v)
        if (args.length === 1) {
            const v = args[0];
            return this.minus(v).length;
        }

        // u.distanceTo(x, y)
        if (args.length === 2) {
            return this.distanceTo(vec(...args));
        }

        // throw error otherwise
        throw new Error(`Vector.distanceTo() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // 🔹 .isEqualTo(v)
    isEqualTo(v, {threshold = 100 * Number.EPSILON}={}) {
        return areEqualNumbers(0, this.distanceTo(v), {threshold});
    }

    // ---------------
    //     debug
    // ---------------

    // 🔹 .toString()
    toString() { return `(${this.x}, ${this.y})` }

    // ⭐️ custom type name
    // ❗️ note: typeof vec(1,2) === 'object' 
    get [Symbol.toStringTag]() { return 'Vector' }
}

// ----------------------------------------
// ⭐️ convenience factory functions
// ----------------------------------------

// vec(x, y)
function vec(x, y) { return new Vector(x, y) }

// polar(r, theta)
function polar(r, theta) { 
    return vec(r * cos(theta), r * sin(theta)) 
}

// linear combination: a1*v1 + a2*v2 + ... + an*vn
function linearCombination(scalars, vectors) {
    return vectors
        .map((v, i) => v.times(scalars[i] || 0))
        .reduce((sum, vec) => sum.plus(vec), Vector.zero);
}

// --------------
// ⭐️ helpers
// --------------

// ⭐️ areEqualNumbers(x, y {threshold})
// - Number.EPSILON = 2^(-52) ≈ 2.2 * 10^(-16)
function areEqualNumbers(x, y, {threshold = Number.EPSILON}={}) { 
    return Math.abs(x - y) < threshold; 
}

// 📤 export
// -----------------------------
// export { vec, polar, Vector, linearCombination };            // export ES module
module.exports = { vec, polar, Vector, linearCombination };
```
{% endtab %}

{% tab title="2" %}
* :floppy\_disk: replit ⟩ [Vector.js](https://replit.com/@pegasusroe/Vectorjs#custom/Vector.js) ,  require -> [number+ext](../../../js/val/prim/num/number+ext "mention")

```javascript
// 2023.01.21 - 13:18 (+) .clampInRect()
// 2023.01.15 - 19:56 (+) linearCombination()
// 2023.01.15 - 08:50 (?) refactor, first recorded.
// ------------------------------------------------------

const {sin, cos, sqrt} = Math;

// ⭐️ import 
// ------------------------------------------------------
import {  } from './Number+ext.js';    // 👔 Number + ext


// ⭐️ Vector
// ------------------------------------------------------
// - vec(x, y)
// - polar(r, theta)
// - linearCombination(scalars, vectors)
// ------------------------------------------------------
// 🔸 .x
// 🔸 .y
// 🔸 .coords         : [x, y]
// 🔸 .inverse        : -v            (additive inverse)
// 🔹 .plus(...args)  : (v), (x, y)
// 🔹 .minus(...args) : (v), (x, y)
// 🔹 .times(k)       : u * k         (scalar product)
// ------------------------------------------------------
// 🔸 .length
// 🔹 .distanceTo(...args)    : (v), (x, y)
// ------------------------------------------------------
// 🔹 .toString()
// ------------------------------------------------------
// 
class Vector {

    // 🟧 Vector.zero
    static get zero() { return new Vector(0, 0) }

    // init
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    // 🔸 .coords
    get coords() { return [this.x, this.y] }

    // -----------
    //     -v
    // -----------

    // 🔸 .inverse
    get inverse() { return new Vector(-this.x, -this.y) }
    
    // -------------
    //     u + v
    // -------------

    // 🔹 u.plus(v) or u.plus(x, y)
    plus(...args) {

        // treat as u + v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x + v.x, this.y + v.y)
        }

        // treat as u + (x, y)
        if (args.length === 2) return this.plus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.plus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     u - v
    // -------------

    // 🔹 u.minus(v) or u.minus(x, y)
    minus(...args) { 
        // treat as u - v
        if (args.length === 1) {
            const v = args[0];
            return new Vector(this.x - v.x, this.y - v.y);
        }

        // treat as u - (x, y)
        if (args.length === 2) return this.minus(vec(...args));

        // throw error otherwise
        throw new Error(`Vector.minus() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // -------------
    //     k * v
    // -------------

    // 🔹 .times(k)
    times(k) { return new Vector(this.x * k, this.y * k) }

    // ------------------------
    //     distance related
    // ------------------------

    // 🔸 .length
    get length() { 
        const {x, y} = this;
        return sqrt(x*x + y*y); 
    }

    // 🔹 .distanceTo(v) or .distanceTo(x, y)
    distanceTo(...args) {

        // u.distanceTo(v)
        if (args.length === 1) {
            const v = args[0];
            return this.minus(v).length;
        }

        // u.distanceTo(x, y)
        if (args.length === 2) {
            return this.distanceTo(vec(...args));
        }

        // throw error otherwise
        throw new Error(`Vector.distanceTo() is expecting 1 or 2 arguments, but got ${args.length}.`);
    }

    // 🔹 .isEqualTo(v)
    isEqualTo(v, {threshold = 100 * Number.EPSILON}={}) {
        return areEqualNumbers(0, this.distanceTo(v), {threshold});
    }

    // ---------------
    //     rect
    // ---------------

    clampInRect(rect) {
        const {x: x1, y: y1} = rect.origin;
        const {x: x2, y: y2} = rect.bottomRight;
        return vec(this.x.clamp(x1, x2), this.y.clamp(y1, y2)); // 👔 Number + ext
    }

    // ---------------
    //     debug
    // ---------------

    // 🔹 .toString()
    toString() { return `(${this.x}, ${this.y})` }

    // ⭐️ custom type name
    // ❗️ note: typeof vec(1,2) === 'object' 
    get [Symbol.toStringTag]() { return 'Vector' }
}

// ----------------------------------------
// ⭐️ convenience factory functions
// ----------------------------------------

// vec(x, y)
function vec(x, y) { return new Vector(x, y) }

// polar(r, theta)
function polar(r, theta) { 
    return vec(r * cos(theta), r * sin(theta)) 
}

// linear combination: a1*v1 + a2*v2 + ... + an*vn
function linearCombination(scalars, vectors) {
    return vectors
        .map((v, i) => v.times(scalars[i] || 0))
        .reduce((sum, vec) => sum.plus(vec), Vector.zero);
}

// --------------
// ⭐️ helpers
// --------------

// ⭐️ areEqualNumbers(x, y {threshold})
// - Number.EPSILON = 2^(-52) ≈ 2.2 * 10^(-16)
function areEqualNumbers(x, y, {threshold = Number.EPSILON}={}) { 
    return Math.abs(x - y) < threshold; 
}

// 📤 export
// -----------------------------
export { vec, polar, Vector, linearCombination };            // export ES module
// module.exports = { vec, polar, Vector, linearCombination };
```
{% endtab %}
{% endtabs %}
