graphics/oval.js

import Thing from './thing.js';

/**
 * An Oval is an ellipse, with its horizontal width defined by its .width property and its height defined by its .height property.
 * @class
 * @extends Thing
 */
class Oval extends Thing {
    type = 'Oval';
    anchor = { vertical: 0.5, horizontal: 0.5 };

    /**
     * Constructs a new oval.
     * @constructor
     * @param {number} width - Desired width of the Oval
     * @param {number} height - Desired height of the Oval
     * @example
     * const o = new Oval(20, 10);
     * add(o);
     */
    constructor(width, height) {
        super();
        if (arguments.length !== 2) {
            throw new Error('You should pass exactly 2 arguments to `new Oval(width, height)`.');
        }
        if (typeof width !== 'number' || !isFinite(width)) {
            throw new TypeError(
                'Invalid value for `width`. Make sure you are passing finite numbers to `new Oval(width, height)`. Did you forget the parentheses in `getWidth()` or `getHeight()`? Or did you perform a calculation on a variable that is not a number?'
            );
        }
        if (typeof height !== 'number' || !isFinite(height)) {
            throw new TypeError(
                'Invalid value for `height`. Make sure you are passing finite numbers to `new Oval(width, height)`. Did you forget the parentheses in `getWidth()` or `getHeight()`? Or did you perform a calculation on a variable that is not a number?'
            );
        }
        this.width = Math.max(0, width);
        this.height = Math.max(0, height);
    }
    /**
     * Draws an ellipse centered at this.x and this.y.
     * adapted from http://stackoverflow.com/questions/2172798/
     * how-to-draw-an-oval-in-html5-canvas
     *
     * @private
     * @param {CanvasRenderingContext2D} context - Context to draw on.
     */
    draw(context) {
        super.draw(context, () => {
            context.translate(this.width / 2, this.height / 2);
            context.beginPath();
            context.ellipse(0, 0, this.width / 2, this.height / 2, 2 * Math.PI, 0, 2 * Math.PI);
            context.closePath();
            context.translate(-this.width / 2, -this.height / 2);
        });
    }

    /**
     * Gets the height of the oval.
     *
     * @returns {number} Height of the oval.
     */
    getHeight() {
        return this.height;
    }

    /**
     * Gets the width of the oval.
     *
     * @returns {number} Width of the oval.
     */
    getWidth() {
        return this.width;
    }

    /**
     * Sets the width of the oval.
     *
     * @param {number} width - Desired width of the resulting oval.
     */
    setWidth(width) {
        if (arguments.length !== 1) {
            throw new Error('You should pass exactly 1 argument to `setWidth(width)`.');
        }
        if (typeof width !== 'number' || !isFinite(width)) {
            throw new TypeError(
                'You must pass a finite number to `setWidth(width)`. Did you forget the parentheses in `getWidth()` or `getHeight()`? Or did you perform a calculation on a variable that is not a number?'
            );
        }

        this.width = Math.max(0, width);
    }

    /**
     * Sets the height of the oval.
     *
     * @param {number} height - Desired height of the resulting oval.
     */
    setHeight(height) {
        if (arguments.length !== 1) {
            throw new Error('You should pass exactly 1 argument to `setHeight(height)`.');
        }
        if (typeof height !== 'number' || !isFinite(height)) {
            throw new TypeError(
                'You must pass a finite number to `setHeight(height)`. Did you forget the parentheses in `getWidth()` or `getHeight()`? Or did you perform a calculation on a variable that is not a number?'
            );
        }

        this.height = Math.max(0, height);
    }

    /**
     * Checks if the passed point is contained in the oval.
     * Uses the equation for an oval.
     *
     * @alias Oval#containsPoint
     * @param {number} x - The x coordinate of the point being tested.
     * @param {number} y - The y coordinate of the point being tested.
     * @returns {boolean} Whether the passed point is contained in the circle.
     */
    _containsPoint(x, y) {
        x -= this.width * (0.5 - this.anchor.horizontal);
        y -= this.height * (0.5 - this.anchor.vertical);
        var xRadiusSquared = Math.pow(this.width / 2, 2);
        var yRadiusSquared = Math.pow(this.height / 2, 2);
        var xDifferenceSquared = Math.pow(x - this.x, 2);
        var yDifferenceSquared = Math.pow(y - this.y, 2);

        var result = xDifferenceSquared / xRadiusSquared + yDifferenceSquared / yRadiusSquared;

        return result <= 1;
    }
}

export default Oval;