diff weather_server/static/graph.js @ 33:beedfa8eaa3f

Add grid lines to the graph. - Adds time gridlines on the hour and 15 minutes. - Adds temperature gridlines on 10, 5, and 1 degree intervals. - Increases chart range. - Changes from rounding temperature to floor()ing it.
author Paul Fisher <paul@pfish.zone>
date Sat, 12 Jun 2021 20:22:46 +0000
parents f817fa785c93
children
line wrap: on
line diff
--- a/weather_server/static/graph.js	Sat Jun 12 18:43:49 2021 +0000
+++ b/weather_server/static/graph.js	Sat Jun 12 20:22:46 2021 +0000
@@ -10,6 +10,7 @@
 define("math", ["require", "exports"], function (require, exports) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
+    exports.dewPointC = exports.cToF = void 0;
     function cToF(tempC) {
         return tempC * 9 / 5 + 32;
     }
@@ -28,6 +29,7 @@
 define("graph", ["require", "exports", "math"], function (require, exports, math_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
+    exports.setUp = void 0;
     const HISTORY_SECONDS = 60 * 60 * 4;
     const BUFFER_SECONDS = 300;
     function setUp(root, tempElement, dewPointElement) {
@@ -60,14 +62,16 @@
         chart.resize();
         addEventListener('resize', () => chart.resize());
     }
-    const Y_DEGREE_RANGE = 10;
+    const Y_DEGREE_RANGE = 12;
     const LINE_WIDTH_PX = 2;
+    const GRID_WIDTH_PX = 0.5;
     const FONT_SIZE = '40px';
     class Chart {
-        constructor(element, timeRange, data) {
+        constructor(element, timeRange, data, timezone) {
             this.element = element;
             this.timeRange = timeRange;
             this.data = data;
+            this.timezone = timezone;
             this.canvas = document.createElement('canvas');
             this.element.insertBefore(this.canvas, element.firstChild);
             const unit = element.getElementsByClassName('unit')[0];
@@ -85,12 +89,10 @@
             this.redraw(ctx);
         }
         redraw(ctx) {
-            const stroke = getComputedStyle(this.element).color;
-            const family = getComputedStyle(this.element).fontFamily;
-            ctx.strokeStyle = stroke;
+            ctx.strokeStyle = this.stroke();
             ctx.lineJoin = 'round';
             ctx.lineWidth = LINE_WIDTH_PX;
-            ctx.font = `bold ${FONT_SIZE} ${family}`;
+            ctx.font = `bold ${FONT_SIZE} ${this.font()}`;
             const onScreenData = this.data.filter(([time, _]) => this.timeRange[0] <= time);
             const yRange = calculateYRange(onScreenData.map(d => d[1]));
             const [fullW, fullH] = this.size();
@@ -109,7 +111,8 @@
             ctx.fillStyle = getComputedStyle(this.element).backgroundColor;
             ctx.fill();
             ctx.stroke();
-            ctx.fillStyle = stroke;
+            this.drawGrid(ctx, graphSize, yRange);
+            ctx.fillStyle = this.stroke();
             ctx.textAlign = 'left';
             ctx.textBaseline = 'top';
             ctx.fillText(`${niceNumber(lastPt[1])} ${this.unit}`, center[0] + 5 * LINE_WIDTH_PX, center[1] + yMargin);
@@ -121,13 +124,75 @@
                 16;
             return [margin, -31.5 / 2];
         }
+        drawGrid(ctx, size, yRange) {
+            ctx.save();
+            ctx.lineCap = 'butt';
+            ctx.lineWidth = GRID_WIDTH_PX;
+            this.drawTime(ctx, size);
+            this.drawTemps(ctx, size, yRange);
+            ctx.restore();
+        }
+        drawTime(ctx, [w, h]) {
+            const minutes = minutesOf(this.timeRange, this.timezone);
+            for (const [ts, hhmm] of minutes) {
+                const mins = Number(hhmm.split(':')[1]);
+                let opacity;
+                if (mins === 0) {
+                    opacity = 1;
+                }
+                else if (mins % 15 === 0) {
+                    opacity = 0.5;
+                }
+                else {
+                    continue;
+                }
+                const x = project1D(ts, w, this.timeRange);
+                ctx.save();
+                ctx.globalAlpha = opacity;
+                ctx.beginPath();
+                ctx.moveTo(x, 0);
+                ctx.lineTo(x, h);
+                ctx.stroke();
+                ctx.restore();
+            }
+        }
+        drawTemps(ctx, [w, h], [yLo, yHi]) {
+            const lo = Math.floor(yLo);
+            for (let deg = lo; deg < yHi; deg++) {
+                const ones = deg % 10;
+                let opacity;
+                if (ones === 0) {
+                    opacity = 1;
+                }
+                else if (ones === 5) {
+                    opacity = 0.5;
+                }
+                else {
+                    opacity = 0.15;
+                }
+                const y = project1D(deg, h, [yHi, yLo]);
+                ctx.save();
+                ctx.globalAlpha = opacity;
+                ctx.beginPath();
+                ctx.moveTo(0, y);
+                ctx.lineTo(w, y);
+                ctx.stroke();
+                ctx.restore();
+            }
+        }
         size() {
             const cssSize = this.element.getBoundingClientRect();
             return [cssSize.width, cssSize.height];
         }
+        stroke() {
+            return getComputedStyle(this.element).color;
+        }
+        font() {
+            return getComputedStyle(this.element).fontFamily;
+        }
     }
     function niceNumber(n) {
-        return Math.round(n).toLocaleString('en-us').replace('-', '−');
+        return Math.floor(n).toLocaleString('en-us').replace('-', '−');
     }
     const EDGE_FRACTION = 0.125;
     function calculateYRange(ys) {
@@ -142,15 +207,30 @@
     }
     function project(coord, size, xRange, yRange) {
         const [x, y] = coord;
-        const [xMin, xMax] = xRange;
-        const xSpan = xMax - xMin;
-        const [yMin, yMax] = yRange;
-        const ySpan = yMax - yMin;
+        const [yMax, yMin] = yRange;
         const [xSize, ySize] = size;
         return [
-            (x - xMin) / xSpan * xSize,
-            (yMax - y) / ySpan * ySize,
+            project1D(x, xSize, xRange),
+            project1D(y, ySize, [yMin, yMax]),
         ];
     }
+    function project1D(coord, size, range) {
+        const [min, max] = range;
+        const span = max - min;
+        return (coord - min) / span * size;
+    }
+    const MINUTE = 60;
+    function minutesOf([start, end], zone) {
+        const formatter = new Intl.DateTimeFormat('sv', { timeZone: zone, hour: '2-digit', minute: '2-digit' });
+        const startSeconds = MINUTE * Math.floor(start / MINUTE);
+        const result = [];
+        for (let t = startSeconds; t <= end; t += MINUTE) {
+            result.push([t, goodBitsOnly(formatter.format(new Date(t * 1000)))]);
+        }
+        return result;
+    }
+    function goodBitsOnly(s) {
+        return s.replace(/[^0-9 :-]/, '');
+    }
 });
 //# sourceMappingURL=graph.js.map
\ No newline at end of file