2022-12-07 21:36:02 +00:00
|
|
|
// 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() {
|
2022-12-08 15:23:26 +00:00
|
|
|
if (!this.image) this.image = this.elem.querySelector('.chart img');
|
2022-12-07 21:36:02 +00:00
|
|
|
|
|
|
|
// get available space
|
2022-12-08 15:23:26 +00:00
|
|
|
const availableWidth = 532;
|
|
|
|
const availableHeight = 285;
|
2022-12-07 21:36:02 +00:00
|
|
|
|
2022-12-08 15:23:26 +00:00
|
|
|
this.image.width = availableWidth;
|
|
|
|
this.image.height = availableHeight;
|
2022-12-07 21:36:02 +00:00
|
|
|
|
|
|
|
// get context
|
2022-12-08 15:23:26 +00:00
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
canvas.width = availableWidth;
|
|
|
|
canvas.height = availableHeight;
|
|
|
|
const ctx = canvas.getContext('2d');
|
2022-12-07 21:36:02 +00:00
|
|
|
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;
|
|
|
|
|
2022-12-08 15:23:26 +00:00
|
|
|
// set the image source
|
|
|
|
this.image.src = canvas.toDataURL();
|
|
|
|
|
2022-12-07 21:36:02 +00:00
|
|
|
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'));
|