133 lines
5.2 KiB
TypeScript
133 lines
5.2 KiB
TypeScript
import {AppStore, DisplayMode, getAppState} from "../StateStore";
|
|
import GridWidget, {GridProps} from "./GridWidget";
|
|
import UIComponent from "./UIComponent";
|
|
import Chart, {ScaleId} from "../chart/Chart";
|
|
|
|
class ClimateChartWidget extends UIComponent {
|
|
private readonly skeleton: GridWidget;
|
|
private chart: Chart | null = null;
|
|
private initialised: boolean;
|
|
private displayMode: DisplayMode = "pastMins";
|
|
private latestSnapshotInChartTime: number;
|
|
private readonly canvasElement: HTMLCanvasElement = document.createElement("canvas");
|
|
|
|
constructor(gridProps: GridProps) {
|
|
super();
|
|
this.initialised = false;
|
|
this.canvasElement.className = "chart-canvas";
|
|
this.skeleton = new GridWidget({
|
|
...gridProps,
|
|
body: this.canvasElement,
|
|
});
|
|
const now = new Date().getTime() / 1000;
|
|
this.latestSnapshotInChartTime = now - getAppState().minutesDisplayed * 60;
|
|
this.setupListeners();
|
|
this.updateDisplayMode();
|
|
}
|
|
|
|
updateDimensions() {
|
|
this.canvasElement.width = 0;
|
|
this.canvasElement.height = 0;
|
|
const skelStyle = getComputedStyle(this.skeleton.current());
|
|
this.canvasElement.height = this.skeleton.current().clientHeight
|
|
- Number(skelStyle.paddingTop.slice(0, -2))
|
|
- Number(skelStyle.paddingBottom.slice(0, -2));
|
|
this.canvasElement.width = this.skeleton.current().clientWidth
|
|
- Number(skelStyle.paddingLeft.slice(0, -2))
|
|
- Number(skelStyle.paddingRight.slice(0, -2));
|
|
this.chart.updateLayout();
|
|
}
|
|
|
|
private setupListeners() {
|
|
AppStore().subscribeStoreVal("displayMode", () => this.updateDisplayMode());
|
|
AppStore().subscribeStoreVal("minutesDisplayed", () => this.rerender());
|
|
AppStore().subscribeStoreVal("displayWindow", () => this.rerender());
|
|
AppStore().on("timeseriesUpdated", () => this.rerender());
|
|
AppStore().on("newTimeseries", (timeseries) => this.chart.addTimeseries(timeseries));
|
|
AppStore().subscribeStoreVal("documentReady", async () => {
|
|
await this.initChart();
|
|
this.updateDimensions();
|
|
window.addEventListener("resize", () => {
|
|
this.updateDimensions();
|
|
});
|
|
});
|
|
AppStore().subscribeStoreVal("utcOffset", () => this.updateTimezone());
|
|
AppStore().subscribeStoreVal("highlightedTimeseries", (name) => this.chart.highlightTimeseries(name));
|
|
}
|
|
|
|
private handleScroll(direction: number, magnitude: number, index: number) {
|
|
if (getAppState().displayMode === "pastMins") {
|
|
AppStore().emulateLastMinsWithWindow();
|
|
}
|
|
const displayedWindow = getAppState().displayWindow;
|
|
const beforeIndex = index - displayedWindow.start;
|
|
const afterIndex = displayedWindow.stop - index;
|
|
const factor = direction === 1 ? 1.1 : 0.9;
|
|
const newBeforeIndex = factor * beforeIndex;
|
|
const newAfterIndex = factor * afterIndex;
|
|
AppStore().setDisplayWindow({
|
|
start: index - newBeforeIndex,
|
|
stop: index + newAfterIndex,
|
|
});
|
|
}
|
|
|
|
private handleDrag(deltaX: number, deltaY: number, deltaIndex: number) {
|
|
if (getAppState().displayMode === "pastMins") {
|
|
AppStore().emulateLastMinsWithWindow();
|
|
}
|
|
AppStore().shiftDisplayWindow(deltaIndex);
|
|
}
|
|
|
|
private updateTimezone() {
|
|
const offset = getAppState().utcOffset * 60 * 60 * 1000;
|
|
this.chart.setTimestampFormatter((timestamp) => new Date(timestamp * 1000 + offset).toLocaleTimeString());
|
|
}
|
|
|
|
private async initChart() {
|
|
try {
|
|
AppStore().addLoad();
|
|
const ctx = this.canvasElement.getContext("2d", {alpha: false});
|
|
this.chart = new Chart(ctx);
|
|
getAppState().leftTimeseries.forEach(timeseries => this.chart.addTimeseries(timeseries, ScaleId.Left));
|
|
getAppState().rightTimeseries.forEach(timeseries => this.chart.addTimeseries(timeseries, ScaleId.Right));
|
|
this.chart.on("scroll", (...args) => this.handleScroll(...args));
|
|
this.chart.on("drag", (...args) => this.handleDrag(...args));
|
|
this.initialised = true;
|
|
await this.rerender();
|
|
} catch (e) {
|
|
AppStore().fatalError(e);
|
|
} finally {
|
|
AppStore().finishLoad();
|
|
}
|
|
}
|
|
|
|
private async updateDisplayMode() {
|
|
this.displayMode = getAppState().displayMode;
|
|
await this.rerender();
|
|
}
|
|
|
|
private async rerender() {
|
|
if (!this.initialised) {
|
|
return;
|
|
}
|
|
let start;
|
|
let stop;
|
|
if (this.displayMode === "window") {
|
|
start = getAppState().displayWindow.start;
|
|
stop = getAppState().displayWindow.stop;
|
|
} else if (this.displayMode === "pastMins") {
|
|
const mins = getAppState().minutesDisplayed;
|
|
start = getAppState().lastUpdateTime - mins * 60;
|
|
stop = getAppState().lastUpdateTime;
|
|
}
|
|
this.chart.setRange({start, stop});
|
|
this.chart.render();
|
|
}
|
|
|
|
current() {
|
|
return this.skeleton.current();
|
|
}
|
|
}
|
|
|
|
export default ClimateChartWidget;
|