UNPKG

codehs-graphics

Version:

Helpers used to run graphics problems in the CodeHS editor.

273 lines (242 loc) 8.79 kB
'use strict'; /** * @namespace Arc */ var Thing = require('./thing.js'); var graphicsUtils = require('./graphics-utils.js'); /* The angles are always stored in radians. * Based on the unit (by default, degrees), the angles might be converted * when calling getters or setters on the start/end angles. */ /** * @class Arc * @augments Thing * @param {number} radius - Desired radius of the arc. * @param {number} startAngle - Start angle of the arc. * @param {number} endAngle - End angle of the arc. * @param {number} angleUnit - Integer representing unit. * Degrees ===0, Radians ===1 */ function Arc(radius, startAngle, endAngle, angleUnit) { if (arguments.length !== 4) { throw new Error('You should pass exactly 4 arguments to <span ' + 'class="code">new Arc(raduis, startAngle, endAngle, ' + 'angleUnit)</span>'); } if (typeof radius !== 'number' || !isFinite(radius)) { throw new TypeError('Invalid value for <span class="code">radius' + '</span>. Make sure you are passing finite numbers to <span ' + 'class="code">new Arc(raduis, startAngle, endAngle, ' + 'angleUnit)</span>'); } if (typeof startAngle !== 'number' || !isFinite(startAngle)) { throw new TypeError('Invalid value for <span class="code">startAngle' + '</span>. Make sure you are passing finite numbers to <span ' + 'class="code">new Arc(raduis, startAngle, endAngle, ' + 'angleUnit)</span>'); } if (typeof endAngle !== 'number' || !isFinite(endAngle)) { throw new TypeError('Invalid value for <span class="code">endAngle' + '</span>. Make sure you are passing finite numbers to <span ' + 'class="code">new Arc(raduis, startAngle, endAngle, ' + 'angleUnit)</span>'); } if (typeof angleUnit !== 'number' || !isFinite(angleUnit)) { throw new TypeError('Invalid value for <span class="code">angleUnit' + '</span>. Make sure you are passing finite numbers to <span ' + 'class="code">new Arc(raduis, startAngle, endAngle, ' + 'angleUnit)</span>'); } Thing.call(this); this.radius = radius; this.angleUnit = angleUnit == Arc.DEGREES ? Arc.DEGREES : Arc.RADIANS; this.counterclockwise = Arc.COUNTER_CLOCKWISE; this.type = 'Arc'; if (this.angleUnit == Arc.DEGREES) { startAngle = degreesToRadians(startAngle); endAngle = degreesToRadians(endAngle); } this.startAngle = startAngle; this.endAngle = endAngle; } Arc.prototype = new Thing(); Arc.prototype.constructor = Arc; // Constants for Arcs. Arc.COUNTER_CLOCKWISE = true; Arc.CLOCKWISE = false; Arc.DEGREES = 0; Arc.RADIANS = 1; /** * Draws the arc in the canvas. * * @param {CodeHSGraphics} __graphics__ - Instance of the __graphics__ module. */ Arc.prototype.draw = function(__graphics__) { var context = __graphics__.getContext(); // http://stackoverflow.com/questions/17125632/html5-canvas-rotate-object-without-moving-coordinates context.save(); context.beginPath(); context.translate(this.x, this.y); context.rotate(this.rotation); context.arc(0, 0, this.radius, prepareAngle(this.startAngle), prepareAngle(this.endAngle), this.counterclockwise); context.lineTo(0, 0); if (this.hasBorder) { context.lineWidth = this.lineWidth; context.strokeStyle = this.stroke.toString(); context.stroke(); } context.fillStyle = this.color.toString(); context.fill(); context.restore(); }; /* Sets the starting angle of the arc. * Note: All angles are stored in radians, so we must first convert * to radians (if the unit is degrees) before storing the new angle. * @param {number} angle - The desired start angle of the arc. */ Arc.prototype.setStartAngle = function(angle) { if (arguments.length !== 1) { throw new Error('You should pass exactly 1 argument to ' + '<span class="code">setStartAngle</span>'); } if (typeof angle !== 'number' || !isFinite(angle)) { throw new Error('Invalid value passed to <span class="code">' + 'setStartAngle</span>. Make sure you are passing a ' + 'finite number.'); } if (this.angleUnit == Arc.DEGREES) { angle = degreesToRadians(angle); } this.startAngle = angle; }; /* Sets the ending angle of the arc. * Note: All angles are stored in radians, so we must first convert * to radians (if the unit is degrees) before storing the new angle. * @param {number} angle - The desired end angle of the arc. */ Arc.prototype.setEndAngle = function(angle) { if (arguments.length !== 1) { throw new Error('You should pass exactly 1 argument to ' + '<span class="code">setEndAngle</span>'); } if (typeof angle !== 'number' || !isFinite(angle)) { throw new Error('Invalid value passed to <span class="code">' + 'setEndAngle</span>. Make sure you are passing a ' + 'finite number.'); } if (this.angleUnit == Arc.DEGREES) { angle = degreesToRadians(angle); } this.endAngle = angle; }; /* Gets the starting angle of the arc. * @returns {number} The start angle of the arc. */ Arc.prototype.getStartAngle = function() { var angle = this.startAngle; if (this.angleUnit == Arc.DEGREES) { angle = radiansToDegrees(this.startAngle); } return Math.round(angle); }; /* Gets the starting angle of the arc. * @returns {number} The start angle of the arc. */ Arc.prototype.getEndAngle = function() { var angle = this.endAngle; if (this.angleUnit == Arc.DEGREES) { angle = radiansToDegrees(this.endAngle); } return Math.round(angle); }; /* Gets the direction of the arc (CW or CCW). * @param {boolean} val - Boolean representing CW or CCW. * `True` sets counterclockwise to true. */ Arc.prototype.setDirection = function(val) { if (arguments.length !== 1) { throw new Error('You should pass exactly 1 argument to ' + '<span class="code">setDirection</span>'); } if (typeof val !== 'boolean') { throw new Error('Invalid value passed to <span class="code">' + 'setDirection</span>. Make sure you are passing a ' + 'boolean value. true for counterclockwise, false for clockwise.'); } this.counterclockwise = val; }; /** * Checks if a given point is contained within the arc. We always fill the arc * so it is technically a segment of the circle * * @param {number} x - x coordinate of the point being tested. * @param {number} y - y coordinate of the point being tested. */ Arc.prototype.containsPoint = function(x, y) { // First check whether the point is in the circle var dist = graphicsUtils.getDistance(this.x, this.y, x, y); if (dist > this.radius) { return false; } // Get vector/ angle for the point var vx = x - this.x; var vy = this.y - y; var theta = Math.atan(vy / vx) // Adjust the arctan based on the quadran the point is in using the // position of the arc as the origin // Quadrant II and III if (vx < 0) { theta += Math.PI; // Quadrant IV } else if (vy < 0) { theta += 2 * Math.PI } // Check whether angle is between start and end, take into account fill // direction var betweenCCW = theta >= this.startAngle && theta <= this.endAngle; if (this.counterclockwise) { return betweenCCW; } else { return !betweenCCW; } }; /** * Prepares an angle to be drawn. * * @memberof Arc * @param {number} angle - The angle to be prepared. * @returns {number} The prepared angle. */ var prepareAngle = function(angle) { // First, convert to degrees (may lose some accuracy) angle = radiansToDegrees(angle); angle = Math.round(angle); // The canvas arc angles go clockwise, but we want them // to go counterclockwise (like the unit circle). Here, // we adjust the angle for that. angle = (360 - angle) % 360; angle = degreesToRadians(angle); return angle; }; /** * Helper to convert degrees to radians. * * @memberof Arc * @param {number} angleInDegrees - The angle represented as degrees. * @returns {number} The angle represented as radians. */ var degreesToRadians = function(angleInDegrees) { return angleInDegrees / 180 * Math.PI; }; /** * Helper to convert radians to degrees. * * @memberof Arc * @param {number} angleInRadians - The angle represented as radians. * @returns {number} The angle represented as degrees. */ var radiansToDegrees = function(angleInRadians) { return angleInRadians / Math.PI * 180; }; module.exports = Arc;