graphics/arc.js

  1. 'use strict';
  2. /**
  3. * @namespace Arc
  4. */
  5. var Thing = require('./thing.js');
  6. var graphicsUtils = require('./graphics-utils.js');
  7. /* The angles are always stored in radians.
  8. * Based on the unit (by default, degrees), the angles might be converted
  9. * when calling getters or setters on the start/end angles.
  10. */
  11. /**
  12. * @class Arc
  13. * @augments Thing
  14. * @param {number} radius - Desired radius of the arc.
  15. * @param {number} startAngle - Start angle of the arc.
  16. * @param {number} endAngle - End angle of the arc.
  17. * @param {number} angleUnit - Integer representing unit.
  18. * Degrees ===0, Radians ===1
  19. */
  20. function Arc(radius, startAngle, endAngle, angleUnit) {
  21. if (arguments.length !== 4) {
  22. throw new Error('You should pass exactly 4 arguments to <span ' +
  23. 'class="code">new Arc(raduis, startAngle, endAngle, ' +
  24. 'angleUnit)</span>');
  25. }
  26. if (typeof radius !== 'number' || !isFinite(radius)) {
  27. throw new TypeError('Invalid value for <span class="code">radius' +
  28. '</span>. Make sure you are passing finite numbers to <span ' +
  29. 'class="code">new Arc(raduis, startAngle, endAngle, ' +
  30. 'angleUnit)</span>');
  31. }
  32. if (typeof startAngle !== 'number' || !isFinite(startAngle)) {
  33. throw new TypeError('Invalid value for <span class="code">startAngle' +
  34. '</span>. Make sure you are passing finite numbers to <span ' +
  35. 'class="code">new Arc(raduis, startAngle, endAngle, ' +
  36. 'angleUnit)</span>');
  37. }
  38. if (typeof endAngle !== 'number' || !isFinite(endAngle)) {
  39. throw new TypeError('Invalid value for <span class="code">endAngle' +
  40. '</span>. Make sure you are passing finite numbers to <span ' +
  41. 'class="code">new Arc(raduis, startAngle, endAngle, ' +
  42. 'angleUnit)</span>');
  43. }
  44. if (typeof angleUnit !== 'number' || !isFinite(angleUnit)) {
  45. throw new TypeError('Invalid value for <span class="code">angleUnit' +
  46. '</span>. Make sure you are passing finite numbers to <span ' +
  47. 'class="code">new Arc(raduis, startAngle, endAngle, ' +
  48. 'angleUnit)</span>');
  49. }
  50. Thing.call(this);
  51. this.radius = radius;
  52. this.angleUnit = angleUnit == Arc.DEGREES ? Arc.DEGREES : Arc.RADIANS;
  53. this.counterclockwise = Arc.COUNTER_CLOCKWISE;
  54. this.type = 'Arc';
  55. if (this.angleUnit == Arc.DEGREES) {
  56. startAngle = degreesToRadians(startAngle);
  57. endAngle = degreesToRadians(endAngle);
  58. }
  59. this.startAngle = startAngle;
  60. this.endAngle = endAngle;
  61. }
  62. Arc.prototype = new Thing();
  63. Arc.prototype.constructor = Arc;
  64. // Constants for Arcs.
  65. Arc.COUNTER_CLOCKWISE = true;
  66. Arc.CLOCKWISE = false;
  67. Arc.DEGREES = 0;
  68. Arc.RADIANS = 1;
  69. /**
  70. * Draws the arc in the canvas.
  71. *
  72. * @param {CodeHSGraphics} __graphics__ - Instance of the __graphics__ module.
  73. */
  74. Arc.prototype.draw = function(__graphics__) {
  75. var context = __graphics__.getContext();
  76. // http://stackoverflow.com/questions/17125632/html5-canvas-rotate-object-without-moving-coordinates
  77. context.save();
  78. context.beginPath();
  79. context.translate(this.x, this.y);
  80. context.rotate(this.rotation);
  81. context.arc(0, 0, this.radius, prepareAngle(this.startAngle),
  82. prepareAngle(this.endAngle), this.counterclockwise);
  83. context.lineTo(0, 0);
  84. if (this.hasBorder) {
  85. context.lineWidth = this.lineWidth;
  86. context.strokeStyle = this.stroke.toString();
  87. context.stroke();
  88. }
  89. context.fillStyle = this.color.toString();
  90. context.fill();
  91. context.restore();
  92. };
  93. /* Sets the starting angle of the arc.
  94. * Note: All angles are stored in radians, so we must first convert
  95. * to radians (if the unit is degrees) before storing the new angle.
  96. * @param {number} angle - The desired start angle of the arc.
  97. */
  98. Arc.prototype.setStartAngle = function(angle) {
  99. if (arguments.length !== 1) {
  100. throw new Error('You should pass exactly 1 argument to ' +
  101. '<span class="code">setStartAngle</span>');
  102. }
  103. if (typeof angle !== 'number' || !isFinite(angle)) {
  104. throw new Error('Invalid value passed to <span class="code">' +
  105. 'setStartAngle</span>. Make sure you are passing a ' +
  106. 'finite number.');
  107. }
  108. if (this.angleUnit == Arc.DEGREES) {
  109. angle = degreesToRadians(angle);
  110. }
  111. this.startAngle = angle;
  112. };
  113. /* Sets the ending angle of the arc.
  114. * Note: All angles are stored in radians, so we must first convert
  115. * to radians (if the unit is degrees) before storing the new angle.
  116. * @param {number} angle - The desired end angle of the arc.
  117. */
  118. Arc.prototype.setEndAngle = function(angle) {
  119. if (arguments.length !== 1) {
  120. throw new Error('You should pass exactly 1 argument to ' +
  121. '<span class="code">setEndAngle</span>');
  122. }
  123. if (typeof angle !== 'number' || !isFinite(angle)) {
  124. throw new Error('Invalid value passed to <span class="code">' +
  125. 'setEndAngle</span>. Make sure you are passing a ' +
  126. 'finite number.');
  127. }
  128. if (this.angleUnit == Arc.DEGREES) {
  129. angle = degreesToRadians(angle);
  130. }
  131. this.endAngle = angle;
  132. };
  133. /* Gets the starting angle of the arc.
  134. * @returns {number} The start angle of the arc.
  135. */
  136. Arc.prototype.getStartAngle = function() {
  137. var angle = this.startAngle;
  138. if (this.angleUnit == Arc.DEGREES) {
  139. angle = radiansToDegrees(this.startAngle);
  140. }
  141. return Math.round(angle);
  142. };
  143. /* Gets the starting angle of the arc.
  144. * @returns {number} The start angle of the arc.
  145. */
  146. Arc.prototype.getEndAngle = function() {
  147. var angle = this.endAngle;
  148. if (this.angleUnit == Arc.DEGREES) {
  149. angle = radiansToDegrees(this.endAngle);
  150. }
  151. return Math.round(angle);
  152. };
  153. /* Gets the direction of the arc (CW or CCW).
  154. * @param {boolean} val - Boolean representing CW or CCW.
  155. * `True` sets counterclockwise to true.
  156. */
  157. Arc.prototype.setDirection = function(val) {
  158. if (arguments.length !== 1) {
  159. throw new Error('You should pass exactly 1 argument to ' +
  160. '<span class="code">setDirection</span>');
  161. }
  162. if (typeof val !== 'boolean') {
  163. throw new Error('Invalid value passed to <span class="code">' +
  164. 'setDirection</span>. Make sure you are passing a ' +
  165. 'boolean value. true for counterclockwise, false for clockwise.');
  166. }
  167. this.counterclockwise = val;
  168. };
  169. /**
  170. * Checks if a given point is contained within the arc. We always fill the arc
  171. * so it is technically a segment of the circle
  172. *
  173. * @param {number} x - x coordinate of the point being tested.
  174. * @param {number} y - y coordinate of the point being tested.
  175. */
  176. Arc.prototype.containsPoint = function(x, y) {
  177. // First check whether the point is in the circle
  178. var dist = graphicsUtils.getDistance(this.x, this.y, x, y);
  179. if (dist > this.radius) {
  180. return false;
  181. }
  182. // Get vector/ angle for the point
  183. var vx = x - this.x;
  184. var vy = this.y - y;
  185. var theta = Math.atan(vy / vx)
  186. // Adjust the arctan based on the quadran the point is in using the
  187. // position of the arc as the origin
  188. // Quadrant II and III
  189. if (vx < 0) {
  190. theta += Math.PI;
  191. // Quadrant IV
  192. } else if (vy < 0) {
  193. theta += 2 * Math.PI
  194. }
  195. // Check whether angle is between start and end, take into account fill
  196. // direction
  197. var betweenCCW = theta >= this.startAngle && theta <= this.endAngle;
  198. if (this.counterclockwise) {
  199. return betweenCCW;
  200. } else {
  201. return !betweenCCW;
  202. }
  203. };
  204. /**
  205. * Prepares an angle to be drawn.
  206. *
  207. * @memberof Arc
  208. * @param {number} angle - The angle to be prepared.
  209. * @returns {number} The prepared angle.
  210. */
  211. var prepareAngle = function(angle) {
  212. // First, convert to degrees (may lose some accuracy)
  213. angle = radiansToDegrees(angle);
  214. angle = Math.round(angle);
  215. // The canvas arc angles go clockwise, but we want them
  216. // to go counterclockwise (like the unit circle). Here,
  217. // we adjust the angle for that.
  218. angle = (360 - angle) % 360;
  219. angle = degreesToRadians(angle);
  220. return angle;
  221. };
  222. /**
  223. * Helper to convert degrees to radians.
  224. *
  225. * @memberof Arc
  226. * @param {number} angleInDegrees - The angle represented as degrees.
  227. * @returns {number} The angle represented as radians.
  228. */
  229. var degreesToRadians = function(angleInDegrees) {
  230. return angleInDegrees / 180 * Math.PI;
  231. };
  232. /**
  233. * Helper to convert radians to degrees.
  234. *
  235. * @memberof Arc
  236. * @param {number} angleInRadians - The angle represented as radians.
  237. * @returns {number} The angle represented as degrees.
  238. */
  239. var radiansToDegrees = function(angleInRadians) {
  240. return angleInRadians / Math.PI * 180;
  241. };
  242. module.exports = Arc;