graphs.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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, yName, xName){
  9. canvas.height = canvas.clientHeight;
  10. canvas.width = canvas.clientWidth;
  11. this.canvas = canvas;
  12. this.context = canvas.getContext("2d");
  13. this.left = canvas.clientWidth - (canvas.clientWidth * 0.9);
  14. this.right = canvas.clientWidth * 0.8;
  15. this.top = canvas.clientHeight - (canvas.clientHeight * 0.99);
  16. this.bottom = canvas.clientHeight * 0.9;
  17. this.data = [];
  18. this.max = 0;
  19. this.xName = xName;
  20. this.yName = yName;
  21. this.xRange = [];
  22. this.colors = [];
  23. this.colorIndex = 0;
  24. for(let i = 0; i < 100; i++){
  25. let redRand = Math.floor(Math.random() * 200);
  26. let greenRand = Math.floor(Math.random() * 200);
  27. let blueRand = Math.floor(Math.random() * 200);
  28. this.colors.push(`rgb(${redRand}, ${greenRand}, ${blueRand})`);
  29. }
  30. }
  31. //Add a dataset to the graph to draw
  32. //Inputs:
  33. // data = array containing list of numbers as the data points for the graph
  34. // data[0] will be on the left. data[data.length-1] will be on the right.
  35. // xRange = array containing two elements, start and end for x axis data (currently only dates)
  36. // name = string name for the line. Used for display and finding lines. Each must be unique
  37. addData(data, xRange, name){
  38. data = {
  39. set: data,
  40. colorIndex: this.colorIndex,
  41. name: name
  42. }
  43. this.colorIndex++;
  44. this.data.push(data);
  45. let isChange = false;
  46. for(let point of data.set){
  47. if(point > this.max){
  48. this.max = point;
  49. this.verticalMultiplier = (this.bottom - this.top) / this.max;
  50. this.horizontalMultiplier = (this.right - this.left) / (data.set.length - 1);
  51. isChange = true;
  52. }
  53. }
  54. if(this.xRange.length === 0){
  55. this.xRange = xRange;
  56. isChange = true;
  57. }else{
  58. if(xRange[0] < this.xRange[0]){
  59. this.xRange[0] = xRange[0];
  60. isChange = true;
  61. }
  62. if(xRange[1] > this.xRange[1]){
  63. this.xRange[1] = xRange[1];
  64. isChange = true;
  65. }
  66. }
  67. if(isChange){
  68. this.drawGraph();
  69. }else{
  70. this.drawLine(data);
  71. }
  72. }
  73. //Removes a single data set from the graph and its line
  74. //Inputs:
  75. // id = the unique identifier of the data set that was passed in with addData function
  76. removeData(name){
  77. for(let i = 0; i < this.data.length; i++){
  78. if(this.data[i].name === name){
  79. this.data.splice(i, 1);
  80. break;
  81. }
  82. }
  83. this.drawGraph();
  84. }
  85. //Completely clears all data
  86. //Does not delete the current graph displaying
  87. clearData(){
  88. this.max = 0;
  89. this.data = [];
  90. this.xRange = [];
  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. }
  103. drawLine(data){
  104. for(let i = 0; i < data.set.length - 1; i++){
  105. this.context.beginPath();
  106. this.context.moveTo(this.left + (this.horizontalMultiplier * i), this.bottom - (this.verticalMultiplier * data.set[i]));
  107. this.context.lineTo(this.left + (this.horizontalMultiplier * (i + 1)), this.bottom - (this.verticalMultiplier * data.set[i + 1]));
  108. this.context.strokeStyle = this.colors[data.colorIndex];
  109. this.context.lineWidth = 2;
  110. this.context.stroke();
  111. }
  112. this.context.strokeStyle = "black";
  113. this.drawLegend(data.colorIndex, data.name);
  114. }
  115. drawXAxis(){
  116. this.context.beginPath();
  117. this.context.moveTo(this.left, this.bottom);
  118. this.context.lineTo(this.right, this.bottom);
  119. this.context.lineWidth = 4;
  120. this.context.stroke();
  121. this.context.font = "25px Arial";
  122. this.context.fillText(this.xName, this.right / 2, this.bottom + 50);
  123. this.context.setLineDash([5, 10]);
  124. this.context.font = "10px Arial";
  125. this.context.lineWidth = 1;
  126. if(Object.prototype.toString.call(this.xRange[0]) === '[object Date]'){
  127. 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;
  128. let showDate = new Date(this.xRange[0]);
  129. for(let i = 0; i < diff; i += Math.floor(diff / 10)){
  130. this.context.fillText(showDate.toLocaleDateString("en-US", {month: "short", day: "numeric", year: "2-digit"}), this.left + (this.horizontalMultiplier * i) - 20, this.bottom + 15);
  131. if(i !== 0){
  132. this.context.beginPath()
  133. this.context.moveTo(this.left + (this.horizontalMultiplier * i), this.bottom);
  134. this.context.lineTo(this.left + (this.horizontalMultiplier * i), this.top);
  135. this.context.strokeStyle = "#a5a5a5";
  136. this.context.stroke();
  137. }
  138. showDate.setDate(showDate.getDate() + Math.abs(diff / 10));
  139. }
  140. }
  141. this.context.strokeStyle = "black";
  142. this.context.setLineDash([]);
  143. }
  144. drawYAxis(){
  145. this.context.beginPath();
  146. this.context.moveTo(this.left, this.top);
  147. this.context.lineTo(this.left, this.bottom);
  148. this.context.lineWidth = 2;
  149. this.context.stroke();
  150. this.context.font = "25px Arial";
  151. this.context.fillText(this.yName, 0, this.bottom / 2);
  152. this.context.setLineDash([5, 10]);
  153. this.context.font = "10px Arial";
  154. this.context.lineWidth = 1;
  155. let axisNum = 0;
  156. let verticalIncrement = (this.bottom - this.top) / 10;
  157. let verticalOffset = 0;
  158. do{
  159. this.context.fillText(Math.round(axisNum).toString(), this.left - 20, this.bottom - verticalOffset + 3);
  160. this.context.beginPath();
  161. this.context.moveTo(this.left, this.bottom - verticalOffset);
  162. this.context.lineTo(this.right, this.bottom - verticalOffset);
  163. this.context.strokeStyle = "#a5a5a5";
  164. this.context.stroke();
  165. verticalOffset += verticalIncrement;
  166. axisNum += this.max / 10;
  167. }while(verticalOffset <= (this.bottom - this.top));
  168. this.context.strokeStyle = "black";
  169. this.context.setLineDash([]);
  170. }
  171. drawLegend(colorIndex, name){
  172. let verticalOffset;
  173. for(let i = 0; i < this.data.length; i++){
  174. if(this.data[i].name === name){
  175. verticalOffset = i * 25;
  176. break;
  177. }
  178. }
  179. this.context.beginPath();
  180. this.context.fillStyle = this.colors[colorIndex];
  181. this.context.fillRect(this.right + 50, this.top + 50 + verticalOffset, 10, 10);
  182. this.context.stroke();
  183. this.context.font = "15px Arial";
  184. this.context.fillText(name, this.right + 65, this.top + 60 + verticalOffset);
  185. this.context.fillStyle = "black";
  186. }
  187. }