classes/box.js

/**
 * Created by Alex Bol on 3/7/2017.
 */
"use strict";

module.exports = function(Flatten) {
    /**
     * Class Box represent bounding box of the shape
     * @type {Box}
     */
    Flatten.Box = class Box {
        /**
         *
         * @param {number} xmin - minimal x coordinate
         * @param {number} ymin - minimal y coordinate
         * @param {number} xmax - maximal x coordinate
         * @param {number} ymax - maximal y coordinate
         */
        constructor(xmin=undefined, ymin=undefined, xmax=undefined, ymax=undefined) {
            /**
             * Minimal x coordinate
             * @type {number}
             */
            this.xmin = xmin;
            /**
             * Minimal y coordinate
             * @type {number}
             */
            this.ymin = ymin;
            /**
             * Maximal x coordinate
             * @type {number}
             */
            this.xmax = xmax;
            /**
             * Maximal y coordinate
             * @type {number}
             */
            this.ymax = ymax;
        }

        /**
         * Clones and returns new instance of box
         * @returns {Box}
         */
        clone() {
            return new Box(this.xmin, this.ymin, this.xmax, this.ymax);
        }

        /**
         * Property low need for interval tree interface
         * @returns {Point}
         */
        get low() {
            return new Flatten.Point(this.xmin, this.ymin);
        }

        /**
         * Property high need for interval tree interface
         * @returns {Point}
         */
        get high() {
            return new Flatten.Point(this.xmax, this.ymax);
        }

        /**
         * Property max returns the box itself !
         * @returns {Box}
         */
        get max() {
            return this.clone();
        }

        /**
         * Return center of the box
         * @returns {Point}
         */
        get center() {
            return new Flatten.Point( (this.xmin + this.xmax)/2, (this.ymin + this.ymax)/2 );
        }

        /**
         * Returns true if not intersected with other box
         * @param {Box} other_box - other box to test
         * @returns {boolean}
         */
        not_intersect(other_box) {
            return (
                this.xmax < other_box.xmin ||
                this.xmin > other_box.xmax ||
                this.ymax < other_box.ymin ||
                this.ymin > other_box.ymax
            );
        }

        /**
         * Returns true if intersected with other box
         * @param {Box} other_box - Query box
         * @returns {boolean}
         */
        intersect(other_box) {
            return !this.not_intersect(other_box);
        }

        /**
         * Returns new box merged with other box
         * @param {Box} other_box - Other box to merge with
         * @returns {Box}
         */
        merge(other_box) {
            return new Box(
                this.xmin === undefined ? other_box.xmin : Math.min(this.xmin, other_box.xmin),
                this.ymin === undefined ? other_box.ymin : Math.min(this.ymin, other_box.ymin),
                this.xmax === undefined ? other_box.xmax : Math.max(this.xmax, other_box.xmax),
                this.ymax === undefined ? other_box.ymax : Math.max(this.ymax, other_box.ymax)
            );
        }

        /**
         * Defines predicate "less than" between two boxes. Need for interval index
         * @param {Box} other_box - other box
         * @returns {boolean} - true if this box less than other box, false otherwise
         */
        less_than(other_box) {
            if (this.low.lessThan(other_box.low))
                return true;
            if (this.low.equalTo(other_box.low) && this.high.lessThan(other_box.high))
                return true;
            return false;
        }

        /**
         * Returns true if this box is equal to other box, false otherwise
         * @param {Box} other_box - query box
         * @returns {boolean}
         */
        equal_to(other_box) {
            return (this.low.equalTo(other_box.low) && this.high.equalTo(other_box.high));
        }

        output() {
            return this.clone();
        }

        maximal_val(box1, box2) {
            // return pt1.lessThan(pt2) ? pt2.clone() : pt1.clone();
            return box1.merge(box2);
        }

        val_less_than(pt1, pt2) {
            return pt1.lessThan(pt2);
        }

        /**
         * Set new values to the box object
         * @param {number} xmin - miminal x coordinate
         * @param {number} ymin - minimal y coordinate
         * @param {number} xmax - maximal x coordinate
         * @param {number} ymax - maximal y coordinate
         */
        set(xmin, ymin, xmax, ymax) {
            this.xmin = xmin;
            this.ymin = ymin;
            this.xmax = xmax;
            this.ymax = ymax;
        }

        /**
         * Return string to draw circle in svg
         * @param {Object} attrs - an object with attributes of svg rectangle element,
         * like "stroke", "strokeWidth", "fill" <br/>
         * Defaults are stroke:"black", strokeWidth:"1", fill:"none"
         * @returns {string}
         */
        svg(attrs = {}) {
            let {stroke, strokeWidth, fill, id, className} = attrs;
            // let rest_str = Object.keys(rest).reduce( (acc, key) => acc += ` ${key}="${rest[key]}"`, "");
            let id_str = (id && id.length > 0) ? `id="${id}"` : "";
            let class_str = (className && className.length > 0) ? `class="${className}"` : "";
            let width = this.xmax - this.xmin;
            let height = this.ymax - this.ymin;

            return `\n<rect x="${this.xmin}" y="${this.ymin}" width=${width} height=${height} stroke="${stroke || "black"}" stroke-width="${strokeWidth || 1}" fill="${fill || "none"}" ${id_str} ${class_str} />`;
        };
    };

    /**
     * Shortcut to create new circle
     * @param args
     * @returns {Box}
     */
    Flatten.box = (...args) => new Flatten.Box(...args);

};