1bdb731335e6af68774bfffe13bb085eba1f66e4
[pulsecounter.git] / web / index.html
1 <?xml version="1.0" encoding="utf-8"?>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
3         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4 <html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
5 <head>
6 <script>
7   var dbg;
8   var canvas, ctx;
9   var ww, wh;
10   var hmax, hfact;
11   var tmin, tmax, tfact;
12   var xzero = 40, yzero = 48;
13   var cold_d = [], hot_d = [];
14
15   function showdate(utime) {
16     var dt = new Date(utime*1000);
17     return dt.toLocaleDateString() + " " + dt.toLocaleTimeString();
18   }
19
20   function px(x) {
21     return xzero + ((x - tmin) * tfact);
22   }
23
24   function py(y) { return wh - yzero - (y * hfact);
25   }
26
27   const dow = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
28   const mn = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
29               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
30
31   function getcombx(lo, hi) {
32     var comb = [], lb = [];
33     var trange = tmax - tmin;
34     var inc, inc2, base, t, dt;
35     var label = function(t) { return "<<" + t.toFixed(1) + ">>"; }
36     function tohour(t) { var dt = new Date(t*1000); return dt.getHours(); }
37     function todow(t) { var dt = new Date(t*1000); return dow[dt.getDay()]; }
38     function todom(t) { var dt = new Date(t*1000); return dt.getDate(); }
39     function tomonth(t) { var dt = new Date(t*1000); return mn[dt.getMonth()]; }
40
41     if (trange < 172800) { /* two days -> one hour */
42       inc = 3600;
43       inc2 = 21600;
44       label = tohour;
45     }
46     else if (trange < 864000) { /* 10 days -> six hours */
47       inc = 21600;
48       inc2 = 86400;
49       label = todow;
50     }
51     else if (trange < 2678400) { /* 31 days -> 1 day */
52       inc = 86400;
53       inc2 = 86400;
54       label = todom;
55     }
56     else { /* ~ one month */
57       /* TODO: make this a separate case with loop over months rather than
58          fixed number of seconds. */
59       inc = 2592000;
60       inc2 = 2592000;
61       label = tomonth;
62     }
63
64     dt = new Date(tmin*1000);
65     base = (Math.floor(tmin / inc) + 1) * inc + 60 * dt.getTimezoneOffset();
66     for (t = base; t < tmax; t += inc)
67       comb.push(t);
68     base = (Math.floor(tmin / inc2) + 1) * inc2 + 60 * dt.getTimezoneOffset();
69     for (t = base; t < tmax; t += inc2)
70       lb.push([t, label(t)]);
71     //dbg.innerHTML = "ord=" + ord + "<br>inc=" + inc + "<br>"
72     //              + comb + "<br>" + lb;
73     return [comb, lb];
74   }
75
76   function xaxis() {
77     var comb = getcombx(0, hmax);
78     var i;
79
80     ctx.beginPath();
81     for (i = 0; comb[0][i]; i++) {
82       ctx.moveTo(px(comb[0][i]), py(0) + 5);
83       ctx.lineTo(px(comb[0][i]), py(0));
84     }
85     ctx.strokeStyle = "gray";
86     ctx.stroke();
87
88     ctx.beginPath();
89     ctx.moveTo(px(tmin), py(0));
90     ctx.lineTo(px(tmax), py(0));
91     ctx.strokeStyle = "black";
92     ctx.stroke();
93
94     ctx.fillStyle = "black";
95     ctx.font = "bold 16px Courier";
96     ctx.textAlign = "center";
97     ctx.beginPath();
98     for (i = 0; comb[1][i]; i++) {
99       ctx.fillText(comb[1][i][1], px(comb[1][i][0]), py(0) + 20);
100       ctx.moveTo(px(comb[1][i][0]), py(0) + 5);
101       ctx.lineTo(px(comb[1][i][0]), py(0));
102     }
103     ctx.strokeStyle = "black";
104     ctx.stroke();
105
106     ctx.fillStyle = "black";
107     ctx.textAlign = "left";
108     ctx.fillText(showdate(tmin), px(tmin), py(0) + 40);
109     ctx.textAlign = "right";
110     ctx.fillText(showdate(tmax), px(tmax), py(0) + 40);
111   }
112
113   function getcomby(lo, hi) {
114     var comb = [], lb = [];
115     var d = hi - lo;
116     var ord = Math.pow(10, Math.floor(Math.log10(d)));
117     var scl = Math.floor(d / ord);
118     var inc, inc2, first, x, lb;
119
120     if (scl < 2) { inc = 0.1; inc2 = 0.2; }
121     else if (scl < 5) { inc = 0.1; inc2 = 0.5; }
122     else { inc = 0.5; inc2 = 1; }
123     inc *= ord;
124     inc2 *= ord;
125     first = (Math.floor(lo / inc) + 1) * inc;
126     for (x = 0; x < (d / inc) - 1.2; x++)
127       comb.push(first + inc * x);
128     first = (Math.floor(lo / inc2) + 1) * inc2;
129     for (x = 0; x < (d / inc2) - 1.2; x++)
130       lb.push(first + inc2 * x);
131     //dbg.innerHTML = "ord=" + ord + "<br>inc=" + inc + "<br>"
132     //              + comb + "<br>" + lb;
133     return [comb, lb];
134   }
135
136   function yaxis() {
137     var comb = getcomby(0, hmax);
138     var i;
139
140     ctx.beginPath();
141     for (i = 0; comb[0][i]; i++) {
142       ctx.moveTo(px(tmin) - 5, py(comb[0][i]));
143       ctx.lineTo(px(tmax), py(comb[0][i]));
144     }
145     ctx.strokeStyle = "lightgray";
146     ctx.stroke();
147
148     ctx.beginPath();
149     ctx.moveTo(px(tmin), py(0));
150     ctx.lineTo(px(tmin), py(hmax));
151     ctx.strokeStyle = "black";
152     ctx.stroke();
153
154     ctx.fillStyle = "black";
155     ctx.font = "bold 16px Courier";
156     ctx.textAlign = "right";
157     ctx.fillText(0, px(tmin) - 6, py(0));
158     ctx.beginPath();
159     for (i = 0; comb[1][i]; i++) {
160       ctx.fillText(comb[1][i].toFixed(1), px(tmin) - 6, py(comb[1][i]));
161       ctx.moveTo(px(tmin) - 5, py(comb[1][i]));
162       ctx.lineTo(px(tmax), py(comb[1][i]));
163     }
164     ctx.strokeStyle = "gray";
165     ctx.stroke();
166
167     ctx.textAlign = "left";
168     ctx.fillText("l/min", px(tmin) + 4, py(hmax) + 12);
169   }
170
171   /* @ updates global var `hmax` */
172   function differentiate(times) {
173     var res = [];
174     var dv, dt, v;
175
176     for (i = 0; i < times.length - 1; i++) {
177       dv = times[i+1][1] - times[i][1];
178       dt = times[i+1][0] - times[i][0];
179       if (dt != 0 && dv != 0) {
180         v = (dv / dt) * 600 ; /* Litres per min */
181         if (hmax < v) hmax = v;
182         res.push([times[i][0], v]);
183       }
184     }
185     if (i) res.push([times[i][0], v]);
186
187     return res;
188   }
189
190   function drawplot(data, color) {
191     var i;
192
193     ctx.beginPath();
194     ctx.moveTo(px(data[0][0]), py(data[0][1]));
195     for (i = 1; i < data.length; i++) {
196       ctx.lineTo(px(data[i][0]), py(data[i - 1][1]));
197       ctx.lineTo(px(data[i][0]), py(data[i][1]));
198     }
199     ctx.strokeStyle = color;
200     ctx.stroke();
201   }
202
203   function showloading() {
204     ctx.fillStyle = "green";
205     ctx.font = "bold 16px Courier";
206     ctx.textAlign="center";
207     ctx.fillText("...loading...", (ww / 2) , (wh / 2) + 8);
208   }
209
210   function showempty() {
211     ctx.fillStyle = "red";
212     ctx.font = "bold 24px Courier";
213     ctx.textAlign="center";
214     ctx.fillText("No data for the requested time interval",
215                  (ww / 2) , (wh / 2) + 8);
216   }
217
218   function clearplot() {
219     ctx.clearRect(0, 0, ww, wh);
220   }
221
222   function redraw() {
223     clearplot();
224     if (cold_d.length || hot_d.length) {
225       tfact = (ww - xzero) / (tmax - tmin);
226       hfact = (wh - yzero) / hmax;
227       xaxis();
228       yaxis();
229       drawplot(cold_d, "blue");
230       drawplot(hot_d, "red");
231     } else {
232       showempty();
233     }
234   }
235
236   function gotdata(data) {
237     document.getElementById("curcold").innerHTML =
238       (data.current.cold / 100).toFixed(2);
239     document.getElementById("curhot").innerHTML =
240       (data.current.hot / 100).toFixed(2);
241
242     document.getElementById("totcold").innerHTML =
243       ((data.cold[data.cold.length - 1][1] - data.cold[0][1]) * 10);
244     document.getElementById("tothot").innerHTML =
245       ((data.hot[data.hot.length - 1][1] - data.hot[0][1]) * 10);
246
247     tmin = data.range.lo;
248     tmax = data.range.hi;
249     //dbg.innerHTML = "from " + tmin + " to " + tmax
250     //              + "<br>from " + showdate(tmin) + " to " + showdate(tmax);
251     /* differetiate() updates hmax */
252     hmax = 0;
253     cold_d = differentiate(data.cold);
254     hot_d = differentiate(data.hot);
255     //dbg.innerHTML = "hmax=" + hmax + " hfact=" + hfact + "<br>"
256     //              + cold_d + "<br>" + hot_d;
257     redraw();
258   }
259
260   function sendreq(qstr) {
261     var url = "query.cgi" + qstr;
262     var xmlhttp = new XMLHttpRequest();
263
264     //dbg.innerHTML = url;
265     xmlhttp.onreadystatechange = function() {
266       if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
267         // dbg.innerHTML = xmlhttp.responseText;
268         var myData = JSON.parse(xmlhttp.responseText);
269         gotdata(myData);
270       }
271     }
272     xmlhttp.open("GET", url, true);
273     clearplot();
274     showloading();
275     xmlhttp.send();
276   }
277
278   function iso2qu(idate) {
279     return idate.replace("T", "+").replace("0Z", "");
280   }
281
282   function sendquery(lo, hi) {
283     return sendreq("?lo=" + iso2qu(lo) + "&hi=" + iso2qu(hi));
284   }
285
286   function resize() {
287     ww = window.innerWidth - 4;
288     if (ww > window.innerHeight) ww = window.innerHeight;
289     wh = ww / 2;
290     canvas.width = ww;
291     canvas.height = wh;
292     canvas.style.width = ww + "px";
293     canvas.style.height = wh + "px";
294     redraw();
295   }
296
297   function daystart(date) {
298     date.setMilliseconds(0);
299     date.setSeconds(0);
300     date.setMinutes(0);
301     date.setHours(0);
302     return date;
303   }
304
305   function prevweek() {
306     var tdy = daystart(new Date());
307     var dow = tdy.getDay();
308     var wstart, wend;
309
310     wstart = new Date(1*tdy - 86400000 * (dow + 7));
311     wend = new Date(1*wstart + 86400000 * 7);
312     sendquery(wstart.toISOString(), wend.toISOString());
313   }
314
315   function thisweek() {
316     var tdy = daystart(new Date());
317     var dow = tdy.getDay();
318     var wstart, wend;
319
320     wstart = new Date(1*tdy - 86400000 * dow);
321     wend = new Date(1*wstart + 86400000 * 7);
322     sendquery(wstart.toISOString(), wend.toISOString());
323   }
324
325   function beforeyesterday() {
326     var tdy = daystart(new Date());
327     var ytd = new Date(1*tdy - 86400000);
328     var byd = new Date(1*ytd - 86400000);
329     sendquery(byd.toISOString(), ytd.toISOString());
330   }
331
332   function yesterday() {
333     var tdy = daystart(new Date());
334     var ytd = new Date(1*tdy - 86400000);
335     sendquery(ytd.toISOString(), tdy.toISOString());
336   }
337
338   function today() {
339     var tdy = daystart(new Date());
340     var tmr = new Date(1*tdy + 86400000);
341     sendquery(tdy.toISOString(), tmr.toISOString());
342   }
343
344   function initialize() {
345     var qstr = window.location.search;
346
347     dbg = document.getElementById("debug");
348     canvas = document.getElementById("plot");
349     ctx = canvas.getContext("2d");
350     resize();
351     if (qstr) sendreq(qstr);
352     else today();
353     
354     document.getElementById("today").onclick = today;
355     document.getElementById("yesterday").onclick = yesterday;
356     document.getElementById("beforeyesterday").onclick = beforeyesterday;
357     document.getElementById("thisweek").onclick = thisweek;
358     document.getElementById("prevweek").onclick = prevweek;
359   }
360
361   /* Set up */
362   window.onload = initialize;
363   window.onresize = resize;
364 </script>
365 <style>
366 @font-face {
367   font-family: PipeDream;
368   src: url('PIPED.TTF') format('truetype');
369   /* Free to use font from http://www.mlink.net/~paterson/jpfonts.htm */
370 }
371 h1 {
372   margin-top: 5px;
373   text-align: center;
374   font-family: PipeDream;
375   font-size: 64px;
376   font-weight: normal;
377   background-color: lightgray;
378 }
379 br {
380   clear: both;
381 }
382 div#currentvals {
383   width: 18em;
384   margin-left: auto;
385   margin-right: auto;
386   margin-bottom: 10px;
387   text-align: center;
388   font-size: 150%;
389 }
390 div#totalvals {
391   width: 18em;
392   margin-left: auto;
393   margin-right: auto;
394   margin-bottom: 10px;
395   text-align: center;
396   font-size: 100%;
397 }
398 div.current {
399   position: relative;
400   padding: 0.2em;
401   border: solid 1px black;
402   margin: 0.2em;
403 }
404 div.cold {
405   float: left;
406   background-color: #d0e0ff;
407 }
408 div.hot {
409   float: right;
410   background-color: #ffd0e0;
411 }
412 canvas#plot {
413   padding: 0px;
414   margin: auto;
415   display: block;
416   width: 640px;
417   height: 320px;
418   /* border: solid 1px black; */
419 }
420 div#queries {
421   margin-left: auto;
422   margin-right: auto;
423   margin-bottom: 10px;
424   text-align: center;
425 }
426 div.query {
427   display: inline-block;
428   position: relative;
429   width: 8em;
430   height: 8em;
431   border: solid 1px black;
432   border-radius: 1em;
433   background-color: lightgray;
434   cursor: pointer;
435 }
436 div.label {
437   display: block;
438   width: 100%;
439   position: absolute;
440   top: 50%;
441   transform: translate(0, -50%);
442 }
443 body {
444   margin: 0px;
445 }
446 </style>
447 <title>Water Meters</title>
448 </head><body>
449 <h1>WATER METERS</h1>
450 <div id="currentvals">
451   Current Readings (m<sup>3</sup>)
452   <div class="current cold" id="curcold">cold</div>
453   <div class="current hot" id="curhot">hot</div>
454 </div>
455 <br />
456 <canvas id="plot" width="640" height = "320"></canvas>
457 <br />
458 <div id="totalvals">
459   Total for the period (l)
460   <div class="current cold" id="totcold">cold</div>
461   <div class="current hot" id="tothot">hot</div>
462 </div>
463 <br />
464 <div id="queries">
465  <div class="query" id="prevweek"><div class="label">PREVIOUS WEEK</div></div>
466  <div class="query" id="beforeyesterday"><div class="label">DAY
467    BEFORE YESTERDAY</div></div>
468  <div class="query" id="yesterday"><div class="label">YESTERDAY</div></div>
469  <div class="query" id="today"><div class="label">TODAY</div></div>
470  <div class="query" id="thisweek"><div class="label">THIS WEEK</div></div>
471 </div>
472 <br />
473 <div id="debug"></div>
474 </body>