otsdaq_prepmodernization  v2_05_02_indev
CanvasLineChart.js
1 /* Library for charting 2D Line graphs via canvas
2  *
3  * - Supports a live updates using a "phosperous" style display with variable persistance
4  * - based on https://weblogs.asp.net/dwahlin/creating-a-line-chart-using-the-html-5-canvas
5  * with modifications to support more functionality
6  *
7  *
8  * "0" on X-Axis should be min X Value, regardless of what that value is
9  * Need to allow for Negitive Y vals, have to change data rendering and increment labeling
10  * - most likely will have to find y vals above and below max and min y vals that are divisible by the number of hdivs cleanly, then graph and draw the grid/incr labels based on that
11  * - current way is close, but last (topmost) data pt is rendering wrong ( off by lower bound), and need to find a way to ensure that "0" is one of the labels
12  *
13  */
14 
15 
16 var LineChart = function() {
17  var dataCtx; //2D Context for the data itself
18  var gridCtx; //2D Context for the grid/labels
19  var gridCanvas
20  var dataCanvas
21  var margins = {top: 50, bottom: 50, left: 75, right: 50}; //margins
22  var chartHeight, chartWidth; // width of the whole canvas area
23  var useGrid = true;
24  var useDataLabels = false;
25  var useAxisLabels = true;
26  var usePersistanceDisplay = false; //bool vals to select what features to use
27  var useIncrLabels = true;
28  var fadeGblAlpha = 0.05; //Global Alpha value for fadeout function
29  var timeoutVal = 150; //Timeout val uses for fadeout function
30  var yMaxPx, xMaxPx; // area of the chart itself, excluding axis/margin etc.
31  var data; //
32  var maxYVal, maxXVal;//max X/Y Values of the data
33  var minXVal, minYVal;//min X/Y Values of the data
34  var xInc, yInc; //Increments for data increment labels
35  var xRatio, yRatio;
36  var hDivs, vDivs;
37  var gridStrokeStyle = "lightgray";
38  var dataStrokeStyle = "black";
39  var dataLineWidth = 2;
40  var ptArray = [];
41 
42 var render = function(dataId,gridId,inData){
43  data = inData;
44  if(data.margins != null){margins=data.margins};
45  if(data.useGrid != null){useGrid=data.useGrid};
46  if(data.useDataLabels != null){useDataLabels=data.useDataLabels};
47  if(data.useAxisLabels != null){useAxisLabels=data.useAxisLabels};
48  if(data.usePersistanceDisplay != null){useAxisLabels=data.useAxisLabels};
49  if(data.fadeGblAlpha != null){fadeGblAlpha=data.fadeGblAlpha};
50  if(data.timeoutVal != null){timeoutVal=data.timeoutVal};
51  if(data.gridStrokeStyle != null){useAxisLabels=data.gridStrokeStyle};
52  if(data.dataStrokeStyle != null){dataStrokeStyle=data.dataStrokeStyle};
53  if(data.dataLineWidth != null){dataLineWidth=data.dataLineWidth};
54  if(data.HorizontalDivs != null){hDivs=data.HorizontalDivs}
55  else{hDivs = data.dataPoints.length-1};
56  if(data.VerticalDivs != null){vDivs=data.VerticalDivs}
57  else{vDivs = data.dataPoints.length-1};
58 
59 
60 
61  gridCanvas = document.getElementById(gridId);
62  gridCtx = gridCanvas.getContext("2d");
63  dataCanvas = document.getElementById(dataId);
64  dataCtx = dataCanvas.getContext("2d");
65 
66  xMaxPx = data.chartWidth;
67  chartWidth = xMaxPx + (margins.left + margins.right);
68  yMaxPx = data.chartHeight;
69  chartHeight = yMaxPx + (margins.top + margins.bottom);
70 
71  maxXVal = getMaxXVal()//Math.round(getMaxXVal() / 10) * 10;
72  maxYVal = Math.round(getMaxYVal() / 10) * 10;
73  minXVal = getMinXVal()//Math.round(getMinXVal() / 10) * 10;
74  minYVal = Math.round(getMinYVal() / 10) * 10;
75 
76  console.log(maxXVal);
77 
78  if(minYVal < 0){
79  yRatio = yMaxPx/(maxYVal-minYVal); //set so that we can use minVal as our lower bound on the chart
80  }
81  else{
82  yRatio = yMaxPx/(maxYVal); //set so that we can use 0 as our lower bound on the chart
83  minYVal = 0;
84  }
85 
86  xRatio = xMaxPx/(maxXVal-minXVal); //min X val is always going to be our leftmost value, so no need to check here
87 
88 
89  console.log(yRatio)
90 
91  dataCanvas.style.height = dataCanvas.height = chartHeight*window.devicePixelRatio;
92  dataCanvas.style.width = dataCanvas.width = chartWidth*window.devicePixelRatio;
93  dataCtx.scale(window.devicePixelRatio,window.devicePixelRatio);
94 
95 
96  gridCanvas.style.height = gridCanvas.height = chartHeight*window.devicePixelRatio;
97  gridCanvas.style.width = gridCanvas.width = chartWidth*window.devicePixelRatio;
98  gridCtx.scale(window.devicePixelRatio,window.devicePixelRatio);
99 
100 
101  //console.log(window.devicePixelRatio);
102 
103 
104  renderChart();
105 };
106 
107 var renderChart = function () { //Render the elements of the chart that use selects along with the data
108  //render things if they're set to be used
109  if (useGrid == true){renderGrid()};
110  if (useDataLabels == true){renderDataLabels()}; //Data labels on the points themself
111  if (useAxisLabels == true){renderAxisLabels()}; //Axis titles/labels
112  if (useIncrLabels == true){renderIncrLabels()}; //Labels on the increments of the grid
113 
114  renderData(data.dataPoints);
115 };
116 
117 var renderGrid = function () {
118  //draw the grid
119 
120  var vDivSpacing = (xMaxPx/vDivs);
121  var hDivSpacing = (yMaxPx/hDivs);
122 
123  gridCtx.beginPath();
124  gridCtx.lineWidth=1;
125 
126  for (var x =margins.left; x <= xMaxPx + margins.left; x += vDivSpacing){ //draw vertical lines
127  gridCtx.moveTo(0.5 + x, margins.top);
128  gridCtx.lineTo(0.5 + x, yMaxPx+margins.top);
129  }
130 
131  for (var x=margins.top; x <= yMaxPx+margins.top; x += hDivSpacing){ //draw horizontal lines
132  gridCtx.moveTo(margins.left, 0.5 + x);
133  gridCtx.lineTo(xMaxPx+margins.left, 0.5 + x);
134  }
135  gridCtx.strokeStyle = gridStrokeStyle;
136  gridCtx.stroke();
137  gridCtx.closePath();
138 
139 };
140 
141 
142 var renderDataLabels = function () {
143 
144  return;
145 };
146 
147 var renderAxisLabels = function () {
148  var labelFont = (data.labelFont != null)?data.labelFont:'20pt Arial';
149  gridCtx.font = labelFont;
150  gridCtx.textAlign = "center";
151 
152  //Render the Title
153  if(data.title != "" && data.title != null){
154  var size = gridCtx.measureText(data.title);
155  gridCtx.fillText(data.title, (margins.left+(xMaxPx/2)), (margins.top/1.5));
156  }
157 
158 
159  //X-Axis Label
160  if(data.xLabel != "" && data.xLabel != null){
161  //console.log(data.xLabel);
162  size = gridCtx.measureText(data.xLabel);
163  gridCtx.fillText(data.xLabel, (margins.left+(xMaxPx/2)), (yMaxPx+margins.top+margins.bottom/1.5))//, margins.left + (xMaxPx/2)-(size.width/2), yMaxPx + (margins.bottom/1.5));
164  }
165 
166  //Y-Axis Label - save the canvas, rotate it to render text for the Y Axis label, then resore it once text rendered
167  if(data.yLabel != "" && data.yLabel != null){
168  gridCtx.save();
169  gridCtx.rotate(-Math.PI / 2);
170  gridCtx.font = labelFont;
171  gridCtx.fillText(data.yLabel, (margins.top+(yMaxPx/ 2)) * -1, margins.left / 4);
172  gridCtx.restore();
173  }
174 
175 }
176 
177 var renderIncrLabels = function() {
178 
179  gridCtx.font = (data.dataPointFont != null) ? data.dataPointFont : '10pt Calibri';
180 
181  if (minYVal < 0){
182  startYLabel = minYVal;
183  }
184  else{
185  startYLabel = 0;
186  }
187 
188 
189  var startXLabel = minXVal;
190 
191  console.log("First X Label: " + startXLabel);
192  console.log("First Y Label: " + startYLabel);
193 
194  var xPos = 0;
195  var yPos = 0;
196  var vDivSpacing = (xMaxPx/vDivs);
197  var hDivSpacing = (yMaxPx/hDivs);
198 
199  gridCtx.beginPath();
200  gridCtx.lineWidth=1;
201 
202 
203 
204 
205  for (var i = 0; i < hDivs+1; i++){
206  yPos += (i == 0) ? margins.top : hDivSpacing;
207  var txt = maxYVal-((i == 0) ? Math.abs(startYLabel) : i*((maxYVal-minYVal)/hDivs));
208  txt = +txt.toFixed(1);
209  console.log(i + " " + txt);
210  var txtSize = gridCtx.measureText(txt);
211  gridCtx.fillText(txt, margins.left - ((txtSize.width >= 14) ? txtSize.width : 10) -7, yPos + 4);
212 
213  }
214 
215  for (var i = 0; i < vDivs+1; i++){
216  xPos += (i == 0) ? margins.left : vDivSpacing;
217  var txt =(i*((maxXVal-minXVal)/vDivs))+startXLabel;
218  txt = +txt.toFixed(1);
219  var txtSize = gridCtx.measureText(txt);
220  gridCtx.fillText(txt, xPos, margins.top + yMaxPx + (margins.bottom / 3));
221  }
222 
223 
224  gridCtx.strokeStyle = gridStrokeStyle;
225  gridCtx.stroke();
226  gridCtx.closePath();
227 
228 }
229 
230 
231 
232 var renderData = function (dataPts) {
233  var lastPt = {x: 0, y: 0};
234  var thisPt = {x: 0, y: 0};
235 
236 
237  dataCtx.beginPath();
238  for(var i = 0; i < data.dataPoints.length; i++){
239  var pt = data.dataPoints[i];
240  thisPt.x = ((pt.x-data.dataPoints[0].x) * xRatio) + margins.left;//treat inital x value as "0", subtract init x from current to do so
241  thisPt.y = ((maxYVal - pt.y) * yRatio) + margins.top; //reversing because canvas coords are weird and start in the top left
242  console.log("Current Point coords - x: " + thisPt.x + " y: " + thisPt.y)
243  if (!(lastPt.x == 0) && !(lastPt.y == 0)){
244  dataCtx.moveTo(lastPt.x+0.5,lastPt.y+0.5);
245  dataCtx.lineTo(thisPt.x+0.5,thisPt.y+0.5);
246  }
247  ptArray.push(thisPt);
248  lastPt.x = thisPt.x;
249  lastPt.y = thisPt.y;
250 
251  }
252  dataCtx.lineJoin = "round";
253  dataCtx.lineCap = "round";
254  dataCtx.strokeStyle = dataStrokeStyle;
255  dataCtx.lineWidth = dataLineWidth;
256  dataCtx.stroke();
257  dataCtx.closePath();
258 
259 };
260 
261 var updateData = function (dataPts){
262  if(!usePersistanceDisplay){
263  dataCtx.clearRect(0, 0, chartWidth, chartHeight);
264  }
265  renderData(dataPts);
266 }
267 
268 var enableFadeOut = function () {
269  usePersistanceDisplay = true;
270  fadeOut();
271 };
272 
273 var disableFadeOut = function () {
274  usePersistanceDisplay = false;
275 };
276 
277 var fadeOut = function () {
278  //https://stackoverflow.com/questions/27082720/html5-apply-transparency-to-canvas-after-drawing-through-javascript
279  if(usePersistanceDisplay == true){
280  dataCtx.save();
281  dataCtx.globalAlpha = fadeGblAlpha;
282  dataCtx.globalCompositeOperation='destination-out';
283  dataCtx.fillStyle= '#FFF';
284  dataCtx.fillRect(0,0,dataCanvas.width, dataCanvas.height);
285  dataCtx.restore();
286 
287  return setTimeout(function() {
288  fadeOut();
289  },timeoutVal);//Tune timeout value (in ms) to adjust rate of fade out, lower will update more often and fade faster
290  }
291  else{
292  return;
293  }
294  };
295 
296 var getMaxXVal = function () {
297  var maxVal = 0;
298  for (let pt of data.dataPoints){
299  if (pt.x > maxVal){
300  maxVal = pt.x
301  }
302  }
303  return maxVal
304 }
305 
306 var getMaxYVal = function () {
307  var maxVal = 0;
308  for (let pt of data.dataPoints){
309  if (pt.y > maxVal){
310  maxVal = pt.y
311  }
312  }
313  return maxVal
314 }
315 
316 
317 var getMinXVal = function () {
318  var minVal = data.dataPoints[0].x;
319  for (let pt of data.dataPoints){
320  if (pt.x < minVal){
321  console.log("X: " + pt.x + " < " + minVal + ", setting minVal to that.")
322  minVal = pt.x
323  }
324  }
325  return minVal
326 }
327 
328 var getMinYVal = function () {
329  var minVal = data.dataPoints[0].y;
330  for (let pt of data.dataPoints){
331  if (pt.y < minVal){
332  minVal = pt.y
333  }
334  }
335  return minVal
336 }
337 
338 
339 
340  return{
341  render: render,
342  enableFadeOut: enableFadeOut,
343  disableFadeOut: enableFadeOut,
344  updateData: updateData
345  };
346 
347 };
348