graphics/line.js

  1. 'use strict';
  2. var Thing = require('./thing.js');
  3. /**
  4. * @class Line
  5. * @augments Thing
  6. * @param {number} x1 - x coordinate of starting point of line.
  7. * @param {number} y1 - y coordinate of starting point of line.
  8. * @param {number} x2 - x coordinate of end point of line.
  9. * @param {number} y2 - y coordinate of end point of line.
  10. */
  11. function Line(x1, y1, x2, y2) {
  12. if (arguments.length !== 4) {
  13. throw new Error(
  14. 'You should pass exactly 4 argument to <span ' +
  15. 'class="code">new Line(x1, y1, x2, y2)</span>'
  16. );
  17. }
  18. if (
  19. typeof x1 !== 'number' ||
  20. typeof y1 !== 'number' ||
  21. typeof x2 !== 'number' ||
  22. typeof y2 !== 'number'
  23. ) {
  24. throw new TypeError(
  25. 'You must pass 4 numbers to <span class="code">' +
  26. 'new Line(x1, y1, x2, y2)</span>. Make sure each parameter you' +
  27. 'are passing is a number.'
  28. );
  29. }
  30. if (!isFinite(x1) || !isFinite(y1) || !isFinite(x2) || !isFinite(y2)) {
  31. throw new TypeError(
  32. 'One or more of the values you passed to <span ' +
  33. 'class="code">new Line(x1, y1, x2, y2)</span> is an illegal ' +
  34. 'number. Did you forget the parentheses in <span class="code">' +
  35. 'getWidth()</span> or <span class="code">getHeight()</span>? Or ' +
  36. 'did you perform a calculation on a variable that is not a ' +
  37. 'number?'
  38. );
  39. }
  40. Thing.call(this);
  41. this.x1 = x1;
  42. this.y1 = y1;
  43. this.x2 = x2;
  44. this.y2 = y2;
  45. this.lineWidth = 2;
  46. this.type = 'Line';
  47. }
  48. Line.prototype = new Thing();
  49. Line.prototype.constructor = Line;
  50. /**
  51. * Sets the color of a line.
  52. *
  53. * @param {Color} color - Sets the color of the line.
  54. */
  55. Line.prototype.setColor = function(color) {
  56. if (arguments.length !== 1) {
  57. throw new Error(
  58. 'You should pass exactly 1 argument to <span ' + 'class="code">setColor(color)</span>.'
  59. );
  60. }
  61. if (color === undefined) {
  62. throw new TypeError('Invalid color');
  63. }
  64. this.stroke = color;
  65. };
  66. /**
  67. * Gets the color of a line.
  68. *
  69. * @returns {Color} Color of the line.
  70. */
  71. Line.prototype.getColor = function() {
  72. return this.stroke;
  73. };
  74. /**
  75. * Draws the line in the canvas.
  76. *
  77. * @param {CodeHSGraphics} __graphics__ - Instance of the __graphics__ module.
  78. */
  79. Line.prototype.draw = function(__graphics__) {
  80. var context = __graphics__.getContext();
  81. // http://stackoverflow.com/questions/17125632/html5-canvas-rotate-object-without-moving-coordinates
  82. context.save();
  83. context.fillStyle = this.color.toString();
  84. context.beginPath();
  85. context.strokeStyle = this.stroke.toString();
  86. context.lineWidth = this.lineWidth;
  87. var rotatedPoints = getRotatedPoints(this.x1, this.y1, this.x2, this.y2, this.rotation);
  88. context.moveTo(rotatedPoints[0], rotatedPoints[1]);
  89. context.lineTo(rotatedPoints[2], rotatedPoints[3]);
  90. context.closePath();
  91. context.stroke();
  92. context.restore();
  93. };
  94. /**
  95. * Gets the new points based on their rotated values.
  96. *
  97. */
  98. /**
  99. * Gets the new points based on their rotated values.
  100. *
  101. * @param {number} x1 X coordinate of start point
  102. * @param {number} y1 Y coordinate of start point
  103. * @param {number} x2 X coordinate of end point
  104. * @param {number} y2 Y Coordinate of end point
  105. * @param {number} rotation radians rotated (Expected in radians)
  106. * @return {array} List of coordinates of both points.
  107. */
  108. var getRotatedPoints = function(x1, y1, x2, y2, rotation) {
  109. var midX = (x1 + x2) / 2;
  110. var midY = (y1 + y2) / 2;
  111. var sinAngle = Math.sin(rotation);
  112. var cosAngle = Math.cos(rotation);
  113. var newX;
  114. var newY;
  115. // Rotate point 1
  116. x1 -= midX;
  117. y1 -= midY;
  118. newX = x1 * cosAngle - y1 * sinAngle;
  119. newY = x1 * sinAngle + y1 * cosAngle;
  120. x1 = newX + midX;
  121. y1 = newY + midY;
  122. // Rotate point 2
  123. x2 -= midX;
  124. y2 -= midY;
  125. newX = x2 * cosAngle - y2 * sinAngle;
  126. newY = x2 * sinAngle + y2 * cosAngle;
  127. x2 = newX + midX;
  128. y2 = newY + midY;
  129. return [x1, y1, x2, y2];
  130. };
  131. /**
  132. * Checks if a given point is contained in the line.
  133. *
  134. * @param {number} x - x coordinate of the point being tested.
  135. * @param {number} y - y coordinate of the point being tested.
  136. */
  137. Line.prototype.containsPoint = function(x, y) {
  138. var betweenXs = (this.x1 <= x && x <= this.x2) || (this.x2 <= x && x <= this.x1);
  139. var betweenYs = (this.y1 <= y && y <= this.y2) || (this.y2 <= y && y <= this.y1);
  140. if (this.x1 == this.x2) {
  141. return this.x1 == x && betweenYs;
  142. } else {
  143. var slope = (this.y2 - this.y1) / (this.x2 - this.x1);
  144. return (
  145. Math.abs(slope * (x - this.x1) - (y - this.y1)) <= this.lineWidth &&
  146. betweenXs &&
  147. betweenYs
  148. );
  149. }
  150. };
  151. /**
  152. * Returns the width of the line.
  153. *
  154. * @returns {number} The width of the line.
  155. */
  156. Line.prototype.getWidth = function() {
  157. return this.width;
  158. };
  159. /**
  160. * Returns the height of the line.
  161. *
  162. * @returns {number} The width of the line.
  163. */
  164. Line.prototype.getHeight = function() {
  165. return this.height;
  166. };
  167. /**
  168. * Sets the width of the line.
  169. *
  170. * @param {number} width - The resulting width of the line.
  171. */
  172. Line.prototype.setLineWidth = function(width) {
  173. if (arguments.length !== 1) {
  174. throw new Error(
  175. 'You should pass exactly 1 argument to <span ' + 'class="code">setLineWidth</span>'
  176. );
  177. }
  178. if (typeof width !== 'number' || !isFinite(width)) {
  179. throw new TypeError(
  180. 'You must pass a finite number to <span class=' +
  181. '"code">setLineWidth(width)</span>. Did you perform a ' +
  182. 'calculation on a variable that is not a number?'
  183. );
  184. }
  185. this.lineWidth = width;
  186. };
  187. /**
  188. * Sets the *starting* point of the line.
  189. *
  190. * @param {number} x - The x coordinate of the resulting ending point.
  191. * @param {number} y - The y coordinate of the resulting ending point.
  192. */
  193. Line.prototype.setStartpoint = function(x, y) {
  194. if (arguments.length !== 2) {
  195. throw new Error(
  196. 'You should pass exactly 2 arguments to <span ' +
  197. 'class="code">setStartpoint(x, y)</span>'
  198. );
  199. }
  200. if (typeof x !== 'number' || !isFinite(x)) {
  201. throw new TypeError(
  202. 'Invalid value for x-coordinate. ' +
  203. 'Make sure you are passing finite numbers to <span ' +
  204. 'class="code">setStartpoint(x, y)</span>. Did you ' +
  205. 'forget the parentheses in <span class="code">getWidth()</span> ' +
  206. 'or <span class="code">getHeight()</span>? Or did you perform a ' +
  207. 'calculation on a variable that is not a number?'
  208. );
  209. }
  210. if (typeof y !== 'number' || !isFinite(y)) {
  211. throw new TypeError(
  212. 'Invalid value for y-coordinate. ' +
  213. 'Make sure you are passing finite numbers to <span ' +
  214. 'class="code">setStartpoint(x, y)</span>. Did you ' +
  215. 'forget the parentheses in <span class="code">getWidth()</span> ' +
  216. 'or <span class="code">getHeight()</span>? Or did you perform a ' +
  217. 'calculation on a variable that is not a number?'
  218. );
  219. }
  220. this.setPosition(x, y);
  221. };
  222. /**
  223. * Sets the *starting* point of the line.
  224. *
  225. * @param {number} x - The x coordinate of the resulting starting point.
  226. * @param {number} y - The y coordinate of the resulting starting point.
  227. */
  228. Line.prototype.setPosition = function(x, y) {
  229. if (arguments.length !== 2) {
  230. throw new Error(
  231. 'You should pass exactly 2 arguments to <span ' +
  232. 'class="code">setPosition(x, y)</span>'
  233. );
  234. }
  235. if (typeof x !== 'number' || !isFinite(x)) {
  236. throw new TypeError(
  237. 'Invalid value for x-coordinate. ' +
  238. 'Make sure you are passing finite numbers to <span ' +
  239. 'class="code">setPosition(x, y)</span>. Did you ' +
  240. 'forget the parentheses in <span class="code">getWidth()</span> ' +
  241. 'or <span class="code">getHeight()</span>? Or did you perform a ' +
  242. 'calculation on a variable that is not a number?'
  243. );
  244. }
  245. if (typeof y !== 'number' || !isFinite(y)) {
  246. throw new TypeError(
  247. 'Invalid value for y-coordinate. ' +
  248. 'Make sure you are passing finite numbers to <span ' +
  249. 'class="code">setPosition(x, y)</span>. Did you ' +
  250. 'forget the parentheses in <span class="code">getWidth()</span> ' +
  251. 'or <span class="code">getHeight()</span>? Or did you perform a ' +
  252. 'calculation on a variable that is not a number?'
  253. );
  254. }
  255. this.x1 = x;
  256. this.y1 = y;
  257. };
  258. /**
  259. * Sets the *ending* point of the line.
  260. *
  261. * @param {number} x - The x coordinate of the resulting ending point.
  262. * @param {number} y - The y coordinate of the resulting ending point.
  263. */
  264. Line.prototype.setEndpoint = function(x, y) {
  265. if (arguments.length !== 2) {
  266. throw new Error(
  267. 'You should pass exactly 2 arguments to <span ' +
  268. 'class="code">setEndpoint(x, y)</span>'
  269. );
  270. }
  271. if (typeof x !== 'number' || !isFinite(x)) {
  272. throw new TypeError(
  273. 'Invalid value for x-coordinate. ' +
  274. 'Make sure you are passing finite numbers to <span ' +
  275. 'class="code">setEndpoint(x, y)</span>. Did you ' +
  276. 'forget the parentheses in <span class="code">getWidth()</span> ' +
  277. 'or <span class="code">getHeight()</span>? Or did you perform a ' +
  278. 'calculation on a variable that is not a number?'
  279. );
  280. }
  281. if (typeof y !== 'number' || !isFinite(y)) {
  282. throw new TypeError(
  283. 'Invalid value for y-coordinate. ' +
  284. 'Make sure you are passing finite numbers to <span ' +
  285. 'class="code">setEndpoint(x, y)</span>. Did you ' +
  286. 'forget the parentheses in <span class="code">getWidth()</span> ' +
  287. 'or <span class="code">getHeight()</span>? Or did you perform a ' +
  288. 'calculation on a variable that is not a number?'
  289. );
  290. }
  291. this.x2 = x;
  292. this.y2 = y;
  293. };
  294. /**
  295. * Moves the entire line.
  296. *
  297. * @param {number} dx - The change in x coordinate of both starting and ending points.
  298. * @param {number} dy - The change in y coordinate of both starting and ending points.
  299. */
  300. Line.prototype.move = function(dx, dy) {
  301. if (arguments.length !== 2) {
  302. throw new Error(
  303. 'You should pass exactly 2 arguments to <span ' + 'class="code">move</span>'
  304. );
  305. }
  306. if (typeof dx !== 'number' || !isFinite(dx)) {
  307. throw new TypeError(
  308. 'Invalid number passed for <span class="code">' +
  309. 'dx</span>. Make sure you are passing finite numbers to <span ' +
  310. 'class="code">move(dx, dy)</span>'
  311. );
  312. }
  313. if (typeof dy !== 'number' || !isFinite(dy)) {
  314. throw new TypeError(
  315. 'Invalid number passed for <span class="code">' +
  316. 'dy</span>. Make sure you are passing finite numbers to <span ' +
  317. 'class="code">move(dx, dy)</span>'
  318. );
  319. }
  320. this.x1 += dx;
  321. this.y1 += dy;
  322. this.x2 += dx;
  323. this.y2 += dy;
  324. };
  325. /**
  326. * Gets the x coordinate of the Line's start point.
  327. *
  328. * @returns {number} The x coordinate of the Line's start point.
  329. */
  330. Line.prototype.getX = function() {
  331. return this.x1;
  332. };
  333. /**
  334. * Gets the y coordinate of the Line's start point.
  335. *
  336. * @returns {number} The y coordinate of the Line's start point.
  337. */
  338. Line.prototype.getY = function() {
  339. return this.y1;
  340. };
  341. /**
  342. * Gets the x coordinate of the Line's start point.
  343. *
  344. * @returns {number} The x coordinate of the Line's start point.
  345. */
  346. Line.prototype.getStartX = function() {
  347. return this.x1;
  348. };
  349. /**
  350. * Gets the y coordinate of the Line's start point.
  351. *
  352. * @returns {number} The y coordinate of the Line's start point.
  353. */
  354. Line.prototype.getStartY = function() {
  355. return this.y1;
  356. };
  357. /**
  358. * Gets the x coordinate of the Line's end point.
  359. *
  360. * @returns {number} The x coordinate of the Line's end point.
  361. */
  362. Line.prototype.getEndX = function() {
  363. return this.x2;
  364. };
  365. /**
  366. * Gets the y coordinate of the Line's end point.
  367. *
  368. * @returns {number} The y coordinate of the Line's end point.
  369. */
  370. Line.prototype.getEndY = function() {
  371. return this.y2;
  372. };
  373. module.exports = Line;