|
|
|
|
@@ -1,6 +1,7 @@
|
|
|
|
|
import Chart from "chart.js/dist/Chart.bundle.min";
|
|
|
|
|
import type {ChartPoint} from "chart.js";
|
|
|
|
|
import {generateClimateChartConfig} from "./climateChartConfig";
|
|
|
|
|
import {config} from "./main";
|
|
|
|
|
|
|
|
|
|
interface Snapshot {
|
|
|
|
|
id: number,
|
|
|
|
|
@@ -25,11 +26,17 @@ class ClimateChart {
|
|
|
|
|
private onLoadedCallback: () => void = () => {};
|
|
|
|
|
private onErrorCallback: (e: Error) => void = () => {};
|
|
|
|
|
private errorLog: string = "";
|
|
|
|
|
private readonly rootUrl: string;
|
|
|
|
|
private readonly canvasId: string;
|
|
|
|
|
private readonly dataEndpointBase: string;
|
|
|
|
|
private readonly domId: string;
|
|
|
|
|
private readonly minutesDisplayed: number = 60;
|
|
|
|
|
|
|
|
|
|
constructor(rootUrl: string, canvasId: string, minutesDisplayed: number) {
|
|
|
|
|
constructor(domId: string, minutesDisplayed: number) {
|
|
|
|
|
this.domId = domId;
|
|
|
|
|
if (config.development) {
|
|
|
|
|
this.dataEndpointBase = "http://tortedda.local/climate/data";
|
|
|
|
|
} else {
|
|
|
|
|
this.dataEndpointBase = "data";
|
|
|
|
|
}
|
|
|
|
|
this.minutesDisplayed = Math.floor(minutesDisplayed);
|
|
|
|
|
if (minutesDisplayed < 0 || Math.floor(minutesDisplayed) !== minutesDisplayed) {
|
|
|
|
|
console.warn(`Minutes passed were ${ minutesDisplayed }, which is invalid. ${ this.minutesDisplayed } minutes are being shown instead.`);
|
|
|
|
|
@@ -37,17 +44,8 @@ class ClimateChart {
|
|
|
|
|
this.initChart().catch((e) => {this.logError(e);});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async getInitialDataBlob(): Promise<SnapshotRecords> {
|
|
|
|
|
const data = await fetch("data?since=" + new Date((new Date().getTime() - this.minutesDisplayed * 60000)).toISOString());
|
|
|
|
|
const payload = await data.json();
|
|
|
|
|
if (payload.snapshots.length < 0) {
|
|
|
|
|
throw new Error("Bad response - no snapshots found!");
|
|
|
|
|
}
|
|
|
|
|
return payload;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async initChart() {
|
|
|
|
|
const canvasElement = document.getElementById(this.canvasId);
|
|
|
|
|
const canvasElement = document.getElementById(this.domId);
|
|
|
|
|
let ctx: CanvasRenderingContext2D;
|
|
|
|
|
if (ClimateChart.isCanvas(canvasElement)) {
|
|
|
|
|
ctx = canvasElement.getContext('2d');
|
|
|
|
|
@@ -59,8 +57,8 @@ class ClimateChart {
|
|
|
|
|
const payload = await this.getInitialDataBlob();
|
|
|
|
|
this.latestSnapshot = payload.snapshots[0];
|
|
|
|
|
this.insertSnapshots(...payload.snapshots);
|
|
|
|
|
this.rerender();
|
|
|
|
|
setInterval(async () => this.updateFromServer().catch(e => this.logError(e)), 30 * 1000);
|
|
|
|
|
this.chart.update();
|
|
|
|
|
this.onLoadedCallback();
|
|
|
|
|
}
|
|
|
|
|
catch (e) {
|
|
|
|
|
@@ -68,18 +66,29 @@ class ClimateChart {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async getInitialDataBlob(): Promise<SnapshotRecords> {
|
|
|
|
|
const minutesAsDate = (new Date().getTime() - this.minutesDisplayed * 60000);
|
|
|
|
|
const dataEndpoint = `${ this.dataEndpointBase }?since=${ new Date(minutesAsDate).toISOString() }`;
|
|
|
|
|
const payload = await (await fetch(dataEndpoint)).json();
|
|
|
|
|
if (payload.snapshots.length < 0) {
|
|
|
|
|
throw new Error("Bad response - no snapshots found!");
|
|
|
|
|
}
|
|
|
|
|
return payload;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async updateFromServer() {
|
|
|
|
|
const lastTimeInChart = (new Date(this.latestSnapshot.time)).toISOString();
|
|
|
|
|
const url = "data?since=" + lastTimeInChart;
|
|
|
|
|
const lastTimeInChart = this.latestSnapshot.time;
|
|
|
|
|
const url = `${ this.dataEndpointBase }?since=${ new Date(this.latestSnapshot.time + "+00:00").toISOString() }`;
|
|
|
|
|
try {
|
|
|
|
|
const payload: SnapshotRecords = await (await fetch(url)).json();
|
|
|
|
|
if (payload.snapshots.length > 0) {
|
|
|
|
|
const latestSnapshotIsNew = new Date(payload.snapshots[0].time).getTime() > new Date(lastTimeInChart).getTime();
|
|
|
|
|
if (latestSnapshotIsNew) {
|
|
|
|
|
this.removeExpiredPointsAfter(lastTimeInChart);
|
|
|
|
|
const newLatestTime = new Date(payload.snapshots[0].time).getTime();
|
|
|
|
|
if (newLatestTime > new Date(lastTimeInChart).getTime()) {
|
|
|
|
|
console.log(payload);
|
|
|
|
|
this.removePointsOlderThan(newLatestTime - this.minutesDisplayed * 60000);
|
|
|
|
|
this.latestSnapshot = payload.snapshots[0];
|
|
|
|
|
this.insertSnapshots(...payload.snapshots);
|
|
|
|
|
this.chart.update();
|
|
|
|
|
this.rerender();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -88,6 +97,10 @@ class ClimateChart {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private rerender() {
|
|
|
|
|
this.chart.update();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private insertSnapshots(...snapshots: Snapshot[]) {
|
|
|
|
|
for (const snapshot of snapshots.reverse()) {
|
|
|
|
|
this.humidityPointList().push({x: snapshot.time, y: snapshot.humidity});
|
|
|
|
|
@@ -96,11 +109,10 @@ class ClimateChart {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private removeExpiredPointsAfter(referenceTime: string) {
|
|
|
|
|
private removePointsOlderThan(referenceTime: number) {
|
|
|
|
|
for (let i = 0; i < this.humidityPointList().length; i++) {
|
|
|
|
|
const timeOnPoint = this.humidityPointList()[i].x;
|
|
|
|
|
const timeElapsedSinceReference = Date.parse(referenceTime) - Date.parse(timeOnPoint);
|
|
|
|
|
if (timeElapsedSinceReference > this.minutesDisplayed * 60000) {
|
|
|
|
|
if (new Date(timeOnPoint).getTime() < referenceTime) {
|
|
|
|
|
this.humidityPointList().splice(i, 1);
|
|
|
|
|
this.tempPointList().splice(i, 1);
|
|
|
|
|
this.co2PointList().splice(i, 1);
|
|
|
|
|
@@ -119,7 +131,7 @@ class ClimateChart {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private co2PointList(): ClimatePoint[] {
|
|
|
|
|
return this.chart.data.datasets[1].data as ClimatePoint[];
|
|
|
|
|
return this.chart.data.datasets[2].data as ClimatePoint[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onLoaded(callback: () => void) {
|
|
|
|
|
|