BoundingRect.ts 7.62 KB
/**
 * @module echarts/core/BoundingRect
 */

import * as matrix from './matrix';
import Point, { PointLike } from './Point';

const mathMin = Math.min;
const mathMax = Math.max;

const lt = new Point();
const rb = new Point();
const lb = new Point();
const rt = new Point();

const minTv = new Point();
const maxTv = new Point();

class BoundingRect {

    x: number
    y: number
    width: number
    height: number

    constructor(x: number, y: number, width: number, height: number) {
        if (width < 0) {
            x = x + width;
            width = -width;
        }
        if (height < 0) {
            y = y + height;
            height = -height;
        }

        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    union(other: BoundingRect) {
        const x = mathMin(other.x, this.x);
        const y = mathMin(other.y, this.y);

        // If x is -Infinity and width is Infinity (like in the case of
        // IncrementalDisplayble), x + width would be NaN
        if (isFinite(this.x) && isFinite(this.width)) {
            this.width = mathMax(
                other.x + other.width,
                this.x + this.width
            ) - x;
        }
        else {
            this.width = other.width;
        }

        if (isFinite(this.y) && isFinite(this.height)) {
            this.height = mathMax(
                other.y + other.height,
                this.y + this.height
            ) - y;
        }
        else {
            this.height = other.height;
        }

        this.x = x;
        this.y = y;
    }

    applyTransform(m: matrix.MatrixArray) {
        BoundingRect.applyTransform(this, this, m);
    }

    calculateTransform(b: RectLike): matrix.MatrixArray {
        const a = this;
        const sx = b.width / a.width;
        const sy = b.height / a.height;

        const m = matrix.create();

        // 矩阵右乘
        matrix.translate(m, m, [-a.x, -a.y]);
        matrix.scale(m, m, [sx, sy]);
        matrix.translate(m, m, [b.x, b.y]);

        return m;
    }

    intersect(b: RectLike, mtv?: PointLike): boolean {
        if (!b) {
            return false;
        }

        if (!(b instanceof BoundingRect)) {
            // Normalize negative width/height.
            b = BoundingRect.create(b);
        }

        const a = this;
        const ax0 = a.x;
        const ax1 = a.x + a.width;
        const ay0 = a.y;
        const ay1 = a.y + a.height;

        const bx0 = b.x;
        const bx1 = b.x + b.width;
        const by0 = b.y;
        const by1 = b.y + b.height;

        let overlap = !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
        if (mtv) {
            let dMin = Infinity;
            let dMax = 0;
            const d0 = Math.abs(ax1 - bx0);
            const d1 = Math.abs(bx1 - ax0);
            const d2 = Math.abs(ay1 - by0);
            const d3 = Math.abs(by1 - ay0);
            const dx = Math.min(d0, d1);
            const dy = Math.min(d2, d3);
            // On x axis
            if (ax1 < bx0 || bx1 < ax0) {
                if (dx > dMax) {
                    dMax = dx;
                    if (d0 < d1) {
                        Point.set(maxTv, -d0, 0); // b is on the right
                    }
                    else {
                        Point.set(maxTv, d1, 0);  // b is on the left
                    }
                }
            }
            else {
                if (dx < dMin) {
                    dMin = dx;
                    if (d0 < d1) {
                        Point.set(minTv, d0, 0); // b is on the right
                    }
                    else {
                        Point.set(minTv, -d1, 0);  // b is on the left
                    }
                }
            }

            // On y axis
            if (ay1 < by0 || by1 < ay0) {
                if (dy > dMax) {
                    dMax = dy;
                    if (d2 < d3) {
                        Point.set(maxTv, 0, -d2); // b is on the bottom(larger y)
                    }
                    else {
                        Point.set(maxTv, 0, d3);  // b is on the top(smaller y)
                    }
                }
            }
            else {
                if (dx < dMin) {
                    dMin = dx;
                    if (d2 < d3) {
                        Point.set(minTv, 0, d2); // b is on the bottom
                    }
                    else {
                        Point.set(minTv, 0, -d3);  // b is on the top
                    }
                }
            }
        }

        if (mtv) {
            Point.copy(mtv, overlap ? minTv : maxTv);
        }
        return overlap;
    }

    contain(x: number, y: number): boolean {
        const rect = this;
        return x >= rect.x
            && x <= (rect.x + rect.width)
            && y >= rect.y
            && y <= (rect.y + rect.height);
    }

    clone() {
        return new BoundingRect(this.x, this.y, this.width, this.height);
    }

    /**
     * Copy from another rect
     */
    copy(other: RectLike) {
        BoundingRect.copy(this, other);
    }

    plain(): RectLike {
        return {
            x: this.x,
            y: this.y,
            width: this.width,
            height: this.height
        };
    }

    /**
     * If not having NaN or Infinity with attributes
     */
    isFinite(): boolean {
        return isFinite(this.x)
            && isFinite(this.y)
            && isFinite(this.width)
            && isFinite(this.height);
    }

    isZero(): boolean {
        return this.width === 0 || this.height === 0;
    }

    static create(rect: RectLike): BoundingRect {
        return new BoundingRect(rect.x, rect.y, rect.width, rect.height);
    }

    static copy(target: RectLike, source: RectLike) {
        target.x = source.x;
        target.y = source.y;
        target.width = source.width;
        target.height = source.height;
    }

    static applyTransform(target: RectLike, source: RectLike, m: matrix.MatrixArray) {
        // In case usage like this
        // el.getBoundingRect().applyTransform(el.transform)
        // And element has no transform
        if (!m) {
            if (target !== source) {
                BoundingRect.copy(target, source);
            }
            return;
        }
        // Fast path when there is no rotation in matrix.
        if (m[1] < 1e-5 && m[1] > -1e-5 && m[2] < 1e-5 && m[2] > -1e-5) {
            const sx = m[0];
            const sy = m[3];
            const tx = m[4];
            const ty = m[5];
            target.x = source.x * sx + tx;
            target.y = source.y * sy + ty;
            target.width = source.width * sx;
            target.height = source.height * sy;
            if (target.width < 0) {
                target.x += target.width;
                target.width = -target.width;
            }
            if (target.height < 0) {
                target.y += target.height;
                target.height = -target.height;
            }
            return;
        }

        // source and target can be same instance.
        lt.x = lb.x = source.x;
        lt.y = rt.y = source.y;
        rb.x = rt.x = source.x + source.width;
        rb.y = lb.y = source.y + source.height;

        lt.transform(m);
        rt.transform(m);
        rb.transform(m);
        lb.transform(m);

        target.x = mathMin(lt.x, rb.x, lb.x, rt.x);
        target.y = mathMin(lt.y, rb.y, lb.y, rt.y);
        const maxX = mathMax(lt.x, rb.x, lb.x, rt.x);
        const maxY = mathMax(lt.y, rb.y, lb.y, rt.y);
        target.width = maxX - target.x;
        target.height = maxY - target.y;
    }
}


export type RectLike = {
    x: number
    y: number
    width: number
    height: number
}

export default BoundingRect;