// hourly forecast list import STATUS from './status.mjs'; import getHourlyData from './hourly.mjs'; import WeatherDisplay from './weatherdisplay.mjs'; import { registerDisplay } from './navigation.mjs'; import { DateTime } from '../vendor/auto/luxon.mjs'; class HourlyGraph extends WeatherDisplay { constructor(navId, elemId, defaultActive) { // special height and width for scrolling super(navId, elemId, 'Hourly Graph', defaultActive); // move the top right data into the correct location on load document.addEventListener('DOMContentLoaded', () => { this.moveHeader(); }); } moveHeader() { // get the header const header = this.fillTemplate('top-right', {}); // place the header this.elem.querySelector('.header .right').append(header); } async getData() { if (!super.getData()) return; const data = await getHourlyData(); // get interesting data const temperature = data.map((d) => d.temperature); const probabilityOfPrecipitation = data.map((d) => d.probabilityOfPrecipitation); const skyCover = data.map((d) => d.skyCover); this.data = { skyCover, temperature, probabilityOfPrecipitation, }; this.setStatus(STATUS.loaded); } drawCanvas() { if (!this.canvas) this.canvas = this.elem.querySelector('.chart canvas'); // get available space const boundingRect = this.canvas.getBoundingClientRect(); const availableWidth = boundingRect.width; const availableHeight = boundingRect.height; this.canvas.width = availableWidth; this.canvas.height = availableHeight; // get context const ctx = this.canvas.getContext('2d'); ctx.imageSmoothingEnabled = false; // calculate time scale const timeScale = calcScale(0, 5, this.data.temperature.length - 1, availableWidth); const startTime = DateTime.now().startOf('hour'); document.querySelector('.x-axis .l-1').innerHTML = formatTime(startTime); document.querySelector('.x-axis .l-2').innerHTML = formatTime(startTime.plus({ hour: 6 })); document.querySelector('.x-axis .l-3').innerHTML = formatTime(startTime.plus({ hour: 12 })); document.querySelector('.x-axis .l-4').innerHTML = formatTime(startTime.plus({ hour: 18 })); document.querySelector('.x-axis .l-5').innerHTML = formatTime(startTime.plus({ hour: 24 })); // order is important last line drawn is on top // clouds const percentScale = calcScale(0, availableHeight - 10, 100, 10); const cloud = createPath(this.data.skyCover, timeScale, percentScale); drawPath(cloud, ctx, { strokeStyle: 'lightgrey', lineWidth: 3, }); // precip const precip = createPath(this.data.probabilityOfPrecipitation, timeScale, percentScale); drawPath(precip, ctx, { strokeStyle: 'aqua', lineWidth: 3, }); // temperature const minTemp = Math.min(...this.data.temperature); const maxTemp = Math.max(...this.data.temperature); const midTemp = Math.round((minTemp + maxTemp) / 2); const tempScale = calcScale(minTemp, availableHeight - 10, maxTemp, 10); const tempPath = createPath(this.data.temperature, timeScale, tempScale); drawPath(tempPath, ctx, { strokeStyle: 'red', lineWidth: 3, }); // temperature axis labels this.elem.querySelector('.y-axis .l-1').innerHTML = maxTemp; this.elem.querySelector('.y-axis .l-2').innerHTML = midTemp; this.elem.querySelector('.y-axis .l-3').innerHTML = minTemp; super.drawCanvas(); this.finishDraw(); } } // create a scaling function from two points const calcScale = (x1, y1, x2, y2) => { const m = (y2 - y1) / (x2 - x1); const b = y1 - m * x1; return (x) => m * x + b; }; // create a path as an array of [x,y] const createPath = (data, xScale, yScale) => data.map((d, i) => [xScale(i), yScale(d)]); // draw a path with shadow const drawPath = (path, ctx, options) => { // first shadow ctx.beginPath(); ctx.strokeStyle = 'black'; ctx.lineWidth = (options?.lineWidth ?? 2) + 2; ctx.moveTo(path[0][0], path[0][1]); path.slice(1).forEach((point) => ctx.lineTo(point[0], point[1] + 2)); ctx.stroke(); // then colored line ctx.beginPath(); ctx.strokeStyle = options?.strokeStyle ?? 'red'; ctx.lineWidth = (options?.lineWidth ?? 2); ctx.moveTo(path[0][0], path[0][1]); path.slice(1).forEach((point) => ctx.lineTo(point[0], point[1])); ctx.stroke(); }; // format as 1p, 12a, etc. const formatTime = (time) => time.toFormat('ha').slice(0, -1); // register display registerDisplay(new HourlyGraph(3, 'hourly-graph'));