graphs.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. //Creates a line graph within a canvas
  2. //Will expand or shrink to the size of the canvas
  3. //Inputs:
  4. // canvas = the canvas that you would like to draw on
  5. // yName = a string for the name of the Y axis
  6. // xName = a string for the name of the X axis
  7. class LineGraph{
  8. constructor(canvas){
  9. this.canvas = canvas;
  10. this.context = canvas.getContext("2d");
  11. this.left = canvas.clientWidth - (canvas.clientWidth * 0.95);
  12. this.right = canvas.clientWidth * 1;
  13. this.top = canvas.clientHeight - (canvas.clientHeight * 1);
  14. this.bottom = canvas.clientHeight * 0.85;
  15. this.data = [];
  16. this.max = 0;
  17. this.xRange = [];
  18. this.colors = [];
  19. this.colorIndex = 0;
  20. for(let i = 0; i < 100; i++){
  21. let redRand = Math.floor(Math.random() * 200);
  22. let greenRand = Math.floor(Math.random() * 200);
  23. let blueRand = Math.floor(Math.random() * 200);
  24. this.colors.push(`rgb(${redRand}, ${greenRand}, ${blueRand})`);
  25. }
  26. }
  27. //Add a dataset to the graph to draw
  28. //Inputs:
  29. // data = array containing list of numbers as the data points for the graph
  30. // data[0] will be on the left. data[data.length-1] will be on the right.
  31. // xRange = array containing two elements, start and end for x axis data (currently only dates)
  32. // name = string name for the line. Used for display and finding lines. Each must be unique
  33. addData(data, xRange, name){
  34. data = {
  35. set: data,
  36. colorIndex: this.colorIndex,
  37. name: name
  38. }
  39. this.colorIndex++;
  40. this.data.push(data);
  41. let isChange = false;
  42. for(let point of data.set){
  43. if(point > this.max){
  44. this.max = point;
  45. this.verticalMultiplier = (this.bottom - this.top) / this.max;
  46. this.horizontalMultiplier = (this.right - this.left) / (data.set.length - 1);
  47. isChange = true;
  48. }
  49. }
  50. if(this.xRange.length === 0){
  51. this.xRange = xRange;
  52. isChange = true;
  53. }else{
  54. if(xRange[0] < this.xRange[0]){
  55. this.xRange[0] = xRange[0];
  56. isChange = true;
  57. }
  58. if(xRange[1] > this.xRange[1]){
  59. this.xRange[1] = xRange[1];
  60. isChange = true;
  61. }
  62. }
  63. if(isChange){
  64. this.drawGraph();
  65. }else{
  66. this.drawLine(data);
  67. }
  68. }
  69. //Removes a single data set from the graph and its line
  70. //Inputs:
  71. // id = the unique identifier of the data set that was passed in with addData function
  72. removeData(name){
  73. for(let i = 0; i < this.data.length; i++){
  74. if(this.data[i].name === name){
  75. this.data.splice(i, 1);
  76. break;
  77. }
  78. }
  79. this.drawGraph();
  80. }
  81. //Completely clears all data
  82. //Does not delete the current graph displaying
  83. clearData(){
  84. this.max = 0;
  85. this.data = [];
  86. this.xRange = [];
  87. }
  88. addTitle(title){
  89. this.top = this.canvas.clientHeight - (this.canvas.clientHeight * 0.9);
  90. this.title = title;
  91. }
  92. /**********
  93. *********PRIVATE*********
  94. **********/
  95. drawGraph(){
  96. this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  97. this.drawYAxis();
  98. this.drawXAxis();
  99. for(let dataSet of this.data){
  100. this.drawLine(dataSet);
  101. }
  102. if(this.title){
  103. this.context.font = "25px Saira";
  104. let xLocation = ((this.right - this.left) / 2) - (this.context.measureText(this.title).width / 2);
  105. this.context.fillText(this.title, xLocation, this.top - 10);
  106. }
  107. }
  108. drawLine(data){
  109. for(let i = 0; i < data.set.length - 1; i++){
  110. this.context.beginPath();
  111. this.context.moveTo(this.left + (this.horizontalMultiplier * i), this.bottom - (this.verticalMultiplier * data.set[i]));
  112. this.context.lineTo(this.left + (this.horizontalMultiplier * (i + 1)), this.bottom - (this.verticalMultiplier * data.set[i + 1]));
  113. this.context.strokeStyle = this.colors[data.colorIndex];
  114. this.context.lineWidth = 2;
  115. this.context.stroke();
  116. }
  117. this.context.strokeStyle = "black";
  118. if(this.data.length > 1){
  119. this.drawLegend(data.colorIndex, data.name);
  120. }
  121. }
  122. drawXAxis(){
  123. this.context.beginPath();
  124. this.context.moveTo(this.left, this.bottom);
  125. this.context.lineTo(this.right, this.bottom);
  126. this.context.lineWidth = 4;
  127. this.context.stroke();
  128. this.context.setLineDash([5, 10]);
  129. this.context.font = "10px Arial";
  130. this.context.lineWidth = 1;
  131. if(Object.prototype.toString.call(this.xRange[0]) === '[object Date]'){
  132. let diff = Math.abs(Math.floor((Date.UTC(this.xRange[0].getFullYear(), this.xRange[0].getMonth(), this.xRange[0].getDate()) - Date.UTC(this.xRange[1].getFullYear(), this.xRange[1].getMonth(), this.xRange[1].getDate())) / (1000 * 60 * 60 * 24))) + 1;
  133. let showDate = new Date(this.xRange[0]);
  134. for(let i = 0; i < diff; i += Math.floor(diff / 10)){
  135. this.context.fillText(showDate.toLocaleDateString("en-US", {month: "short", day: "numeric", year: "2-digit"}), this.left + (this.horizontalMultiplier * i) - 20, this.bottom + 15);
  136. if(i !== 0){
  137. this.context.beginPath()
  138. this.context.moveTo(this.left + (this.horizontalMultiplier * i), this.bottom);
  139. this.context.lineTo(this.left + (this.horizontalMultiplier * i), this.top);
  140. this.context.strokeStyle = "#a5a5a5";
  141. this.context.stroke();
  142. }
  143. showDate.setDate(showDate.getDate() + Math.abs(diff / 10));
  144. }
  145. }
  146. this.context.strokeStyle = "black";
  147. this.context.setLineDash([]);
  148. }
  149. drawYAxis(){
  150. this.context.beginPath();
  151. this.context.moveTo(this.left, this.top);
  152. this.context.lineTo(this.left, this.bottom);
  153. this.context.lineWidth = 2;
  154. this.context.stroke();
  155. this.context.setLineDash([5, 10]);
  156. this.context.font = "10px Arial";
  157. this.context.lineWidth = 1;
  158. let axisNum = 0;
  159. let verticalIncrement = (this.bottom - this.top) / 10;
  160. let verticalOffset = 0;
  161. do{
  162. this.context.fillText(Math.round(axisNum).toString(), this.left - 20, this.bottom - verticalOffset + 3);
  163. this.context.beginPath();
  164. this.context.moveTo(this.left, this.bottom - verticalOffset);
  165. this.context.lineTo(this.right, this.bottom - verticalOffset);
  166. this.context.strokeStyle = "#a5a5a5";
  167. this.context.stroke();
  168. verticalOffset += verticalIncrement;
  169. axisNum += this.max / 10;
  170. }while(verticalOffset <= (this.bottom - this.top));
  171. this.context.strokeStyle = "black";
  172. this.context.setLineDash([]);
  173. }
  174. drawLegend(colorIndex, name){
  175. let verticalOffset;
  176. for(let i = 0; i < this.data.length; i++){
  177. if(this.data[i].name === name){
  178. verticalOffset = i * 25;
  179. break;
  180. }
  181. }
  182. this.context.beginPath();
  183. this.context.fillStyle = this.colors[colorIndex];
  184. this.context.fillRect(this.right + 50, this.top + 50 + verticalOffset, 10, 10);
  185. this.context.stroke();
  186. this.context.font = "15px Arial";
  187. this.context.fillText(name, this.right + 65, this.top + 60 + verticalOffset);
  188. this.context.fillStyle = "black";
  189. }
  190. }
  191. class HorizontalBarGraph{
  192. constructor(canvas){
  193. this.canvas = canvas;
  194. this.context = canvas.getContext("2d");
  195. this.left = 0;
  196. this.right = canvas.clientWidth;
  197. this.top = canvas.clientHeight - (canvas.clientHeight * 0.99);
  198. this.bottom = canvas.clientHeight;
  199. this.data = [];
  200. this.max = 0;
  201. }
  202. //Adds an array of data points to the chart
  203. //All data is removed and redrawn when called
  204. //Must pass in all data points
  205. //Inputs:
  206. // dataArray: array of objects
  207. // num: number for the actual data
  208. // label: text to display on bar
  209. addData(dataArray){
  210. this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  211. for(let point of dataArray){
  212. if(point.num > this.max){
  213. this.max = point.num;
  214. }
  215. this.data.push(point);
  216. }
  217. this.drawGraph();
  218. }
  219. drawGraph(){
  220. let barHeight = ((this.bottom - this.top) / this.data.length) - 2;
  221. for(let i = 0; i < this.data.length; i++){
  222. let topLocation = this.top + (i * barHeight) + 5;
  223. let width = (this.right - this.left) * (this.data[i].num / this.max);
  224. if(this.data[i].num >= this.max){
  225. this.context.fillStyle = "rgb(255, 99, 107)";
  226. }else{
  227. this.context.fillStyle = "rgb(179, 191, 209)";
  228. }
  229. this.context.beginPath();
  230. this.context.fillRect(this.left, topLocation, width, barHeight - 5);
  231. this.context.stroke();
  232. let textLocation = 15;
  233. this.context.font = "12px Saira";
  234. this.context.fillStyle = "black";
  235. this.context.fillText(this.data[i].label, textLocation, (this.top) + (i * barHeight) + (barHeight / 1.5));
  236. }
  237. }
  238. }
  239. module.exports = {
  240. LineGraph: LineGraph,
  241. HorizontalBarGraph: HorizontalBarGraph
  242. }