Mercurial > personal > weather-server
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