Added left and right scales

This commit is contained in:
Daniel Ledda
2021-03-23 16:53:48 +01:00
parent 942db5c18f
commit b7873ab7db
7 changed files with 230 additions and 201 deletions

View File

@@ -100,6 +100,10 @@ h1 {
display: inline-block; display: inline-block;
font-size: 30px; font-size: 30px;
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
transition: background-color 100ms;
}
.display-mode-widget-mins .min-count:hover {
background-color: #eaeaea;
} }
.display-mode-widget-mins input { .display-mode-widget-mins input {
border: none; border: none;
@@ -128,6 +132,12 @@ h1 {
font-size: 12px; font-size: 12px;
} }
.timezone-widget span:nth-child(2) {
transition: background-color 100ms;
}
.timezone-widget span:nth-child(2):hover {
background-color: #eaeaea;
}
.timezone-widget input { .timezone-widget input {
border: none; border: none;
display: inline-block; display: inline-block;
@@ -136,6 +146,3 @@ h1 {
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
background-color: white; background-color: white;
} }
.chart-canvas {
}

View File

@@ -1,14 +1,29 @@
import Timeseries from "./Timeseries"; import Timeseries from "./Timeseries";
interface Scale {
timeseries: Timeseries[];
valRange: {high: number, low: number};
width: number;
}
export enum ScaleId {
Left,
Right
}
const MIN_PIXELS_PER_POINT = 3;
export default class ClimateChart { export default class ClimateChart {
private readonly ctx: CanvasRenderingContext2D; private readonly ctx: CanvasRenderingContext2D;
private readonly timeseries: Timeseries[] = []; private readonly leftScale: Scale;
private readonly rightScale: Scale;
private readonly lastMousePos = {x: 0, y: 0}; private readonly lastMousePos = {x: 0, y: 0};
private readonly indexRange = {start: 0, stop: 0}; private readonly indexRange = {start: 0, stop: 0};
private readonly valRange = {high: -Infinity, low: Infinity} private readonly margins = {top: 20, bottom: 20};
private formatTimestamp = (timestamp: number) => new Date(timestamp * 1000).toLocaleTimeString(); private formatTimestamp = (timestamp: number) => new Date(timestamp * 1000).toLocaleTimeString();
private width = 0; private width = 0;
private height = 0; private height = 0;
private resolution = 1;
constructor(context: CanvasRenderingContext2D) { constructor(context: CanvasRenderingContext2D) {
this.ctx = context; this.ctx = context;
this.ctx.fillStyle = "rgb(255,255,255)"; this.ctx.fillStyle = "rgb(255,255,255)";
@@ -17,6 +32,16 @@ export default class ClimateChart {
this.ctx.fill(); this.ctx.fill();
this.ctx.translate(0.5, 0.5); this.ctx.translate(0.5, 0.5);
this.ctx.canvas.onmousemove = (e) => this.handleMouseMove(e); this.ctx.canvas.onmousemove = (e) => this.handleMouseMove(e);
this.leftScale = {
timeseries: [],
valRange: {high: -Infinity, low: Infinity},
width: 0,
};
this.rightScale = {
timeseries: [],
valRange: {high: -Infinity, low: Infinity},
width: 0,
};
} }
private updateDimensions() { private updateDimensions() {
@@ -24,8 +49,12 @@ export default class ClimateChart {
this.height = Number(getComputedStyle(this.ctx.canvas).height.slice(0, -2)); this.height = Number(getComputedStyle(this.ctx.canvas).height.slice(0, -2));
} }
addTimeseries(timeseries: Timeseries) { addTimeseries(timeseries: Timeseries, scale?: ScaleId) {
this.timeseries.push(timeseries); if (scale === ScaleId.Left) {
this.leftScale.timeseries.push(timeseries);
} else {
this.rightScale.timeseries.push(timeseries);
}
} }
setRange(range: {start: number, stop: number}) { setRange(range: {start: number, stop: number}) {
@@ -33,7 +62,7 @@ export default class ClimateChart {
this.indexRange.stop = range.stop; this.indexRange.stop = range.stop;
} }
handleMouseMove(event: MouseEvent) { private handleMouseMove(event: MouseEvent) {
const {left: canvasX, top: canvasY} = this.ctx.canvas.getBoundingClientRect(); const {left: canvasX, top: canvasY} = this.ctx.canvas.getBoundingClientRect();
const x = event.clientX - canvasX; const x = event.clientX - canvasX;
const y = event.clientY - canvasY; const y = event.clientY - canvasY;
@@ -44,62 +73,112 @@ export default class ClimateChart {
render() { render() {
this.updateDimensions(); this.updateDimensions();
this.ctx.fillStyle = "rgb(255,255,255)"; this.clearCanvas();
this.ctx.fillRect(0, 0, this.width, this.height); this.updateResolution();
this.ctx.fill(); this.setDisplayRangeForScale(this.leftScale);
this.setDisplayRange(); this.setDisplayRangeForScale(this.rightScale);
this.renderScale(); this.renderRightScale();
for (const timeseries of this.timeseries) { this.leftScale.timeseries.forEach(timeseries => this.renderTimeseries(timeseries, ScaleId.Left));
this.renderTimeseries(timeseries); this.rightScale.timeseries.forEach(timeseries => this.renderTimeseries(timeseries, ScaleId.Right));
} this.renderLeftScale();
this.renderTooltips(); this.renderTooltips();
} }
private renderScale() { private clearCanvas() {
this.ctx.fillStyle = "rgb(255,255,255)";
this.ctx.fillRect(0, 0, this.width, this.height);
this.ctx.fill();
}
private updateResolution() {
const chartWidth = (this.width - this.rightScale.width - this.leftScale.width);
const points = this.rightScale.timeseries[0].cachedBetween(this.indexRange.start, this.indexRange.stop).length / 2;
const pixelsPerPoint = chartWidth / points;
if (pixelsPerPoint < MIN_PIXELS_PER_POINT) {
this.resolution = Math.ceil(MIN_PIXELS_PER_POINT / pixelsPerPoint);
}
}
private renderLeftScale() {
this.ctx.fillStyle = "rgb(255,255,255)";
this.ctx.fillRect(0, 0, this.leftScale.width, this.height);
this.ctx.fill();
this.ctx.strokeStyle = "rgb(230,230,230)"; this.ctx.strokeStyle = "rgb(230,230,230)";
this.ctx.fillStyle = "black"; this.ctx.fillStyle = "black";
const ticks = 20; const ticks = 20;
const tickHeight = (this.valRange.high - this.valRange.low) / ticks; const tickHeight = (this.leftScale.valRange.high - this.leftScale.valRange.low) / ticks;
let currentTick = this.valRange.low; let currentTick = this.leftScale.valRange.low - tickHeight;
for (let i = 0; i < ticks; i++) { for (let i = 0; i <= ticks; i++) {
currentTick += tickHeight; currentTick += tickHeight;
const pos = Math.round(this.getY(currentTick)); const text = currentTick.toFixed(2);
const textWidth = this.ctx.measureText(text).width;
if (textWidth > this.leftScale.width) {
this.leftScale.width = textWidth + 10;
}
const pos = Math.round(this.getY(currentTick, ScaleId.Left));
this.ctx.fillText(text, 0, pos + 4);
}
}
private renderRightScale() {
this.ctx.strokeStyle = "rgb(230,230,230)";
this.ctx.fillStyle = "black";
const ticks = 20;
const tickHeight = (this.rightScale.valRange.high - this.rightScale.valRange.low) / ticks;
let currentTick = this.rightScale.valRange.low - tickHeight;
for (let i = 0; i <= ticks; i++) {
currentTick += tickHeight;
const pos = Math.round(this.getY(currentTick, ScaleId.Right));
const text = currentTick.toFixed(2);
const textWidth = this.ctx.measureText(text).width;
if (textWidth > this.rightScale.width) {
this.rightScale.width = textWidth;
}
this.ctx.fillText(text, this.width - textWidth, pos + 4);
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.moveTo(40, pos); this.ctx.moveTo(this.leftScale.width, pos);
this.ctx.lineTo(this.width, pos); this.ctx.lineTo(this.width - textWidth - 5, pos);
this.ctx.stroke(); this.ctx.stroke();
this.ctx.fillText(currentTick.toFixed(2), 0, pos + 4);
} }
} }
private setDisplayRange() { private setDisplayRangeForScale(scale: Scale) {
for (const timeseries of this.timeseries) { for (const timeseries of scale.timeseries) {
const extrema = timeseries.getExtrema(); const extrema = timeseries.getExtrema();
if (extrema.maxVal > this.valRange.high) { if (extrema.maxVal > scale.valRange.high) {
this.valRange.high = extrema.maxVal; scale.valRange.high = extrema.maxVal;
} }
if (extrema.minVal < this.valRange.low) { if (extrema.minVal < scale.valRange.low) {
this.valRange.low = extrema.minVal; scale.valRange.low = extrema.minVal;
} }
} }
} }
private renderTooltips() { private renderTooltips(radius = 20) {
let bestDist = 20; let bestDist = radius;
let bestTimeseries = this.timeseries[0]; let bestTimeseries = this.rightScale.timeseries[0];
let bestIndex = 0; let bestIndex = 0;
let bestVal = 0; let bestVal = 0;
for (const timeseries of this.timeseries) { let bestScale = ScaleId.Right;
const cache = timeseries.cachedBetween(this.indexRange.start, this.indexRange.stop); for (const scaleId of [ScaleId.Left, ScaleId.Right]) {
for (const timeseries of (scaleId === ScaleId.Right ? this.rightScale : this.leftScale).timeseries) {
const cache = timeseries.cachedBetween(
this.getIndex(this.lastMousePos.x - radius / 2),
this.getIndex(this.lastMousePos.x + radius / 2)
);
for (let i = 0; i < cache.length; i += 2) { for (let i = 0; i < cache.length; i += 2) {
const y = this.getY(cache[i], scaleId);
if (y + radius / 2 >= this.lastMousePos.y && y - radius / 2 <= this.lastMousePos.y) {
const x = this.getX(cache[i + 1]); const x = this.getX(cache[i + 1]);
const y = this.getY(cache[i]);
const dist = Math.sqrt((y - this.lastMousePos.y) ** 2 + (x - this.lastMousePos.x) ** 2); const dist = Math.sqrt((y - this.lastMousePos.y) ** 2 + (x - this.lastMousePos.x) ** 2);
if (dist < bestDist) { if (dist < bestDist) {
bestDist = dist; bestDist = dist;
bestTimeseries = timeseries; bestTimeseries = timeseries;
bestIndex = cache[i + 1]; bestIndex = cache[i + 1];
bestVal = cache[i]; bestVal = cache[i];
bestScale = scaleId;
}
}
} }
} }
} }
@@ -107,7 +186,7 @@ export default class ClimateChart {
this.renderTooltip( this.renderTooltip(
`${bestTimeseries.getName()} - (${bestVal.toFixed(2)}, ${this.formatTimestamp(bestIndex)})`, `${bestTimeseries.getName()} - (${bestVal.toFixed(2)}, ${this.formatTimestamp(bestIndex)})`,
this.getX(bestIndex), this.getX(bestIndex),
this.getY(bestVal), this.getY(bestVal, bestScale),
); );
} }
} }
@@ -117,36 +196,43 @@ export default class ClimateChart {
} }
getX(index: number) { getX(index: number) {
return (index - this.indexRange.start) / (this.indexRange.stop - this.indexRange.start) * this.width; return (index - this.indexRange.start) / (this.indexRange.stop - this.indexRange.start) * (this.width - this.rightScale.width - this.leftScale.width) + this.leftScale.width;
} }
getY(value: number) { getY(value: number, scale: ScaleId) {
return this.height - (value - this.valRange.low) / (this.valRange.high - this.valRange.low) * this.height; const valRange = scale === ScaleId.Left ? this.leftScale.valRange : this.rightScale.valRange;
return this.height - (value - valRange.low) / (valRange.high - valRange.low) * (this.height - this.margins.bottom - this.margins.top) - this.margins.top;
} }
getIndex(x: number) { getIndex(x: number) {
return (x / this.width) * this.indexRange.stop; return ((x - this.leftScale.width) / (this.width - this.leftScale.width - this.rightScale.width)) * (this.indexRange.stop - this.indexRange.start) + this.indexRange.start;
} }
getValue(y: number) { getValue(y: number, scale: ScaleId) {
return ((this.height - y) / this.height) * this.valRange.high; const valRange = scale === ScaleId.Left ? this.leftScale.valRange : this.rightScale.valRange;
return ((this.height - y) / this.height) * (valRange.high - valRange.low) + valRange.low;
} }
private renderTimeseries(timeseries: Timeseries) { private renderTimeseries(timeseries: Timeseries, scale: ScaleId) {
const timeseriesPoints = timeseries.cachedBetween(this.indexRange.start, this.indexRange.stop); const timeseriesPoints = timeseries.cachedBetween(this.indexRange.start, this.indexRange.stop);
this.ctx.strokeStyle = timeseries.getColour(); this.ctx.strokeStyle = timeseries.getColour();
const drawBubbles = this.getX(timeseriesPoints[3]) - this.getX(timeseriesPoints[1]) > 6; let y = this.getY(timeseriesPoints[0], scale);
let y = this.getY(timeseriesPoints[0]);
let x = this.getX(timeseriesPoints[1]); let x = this.getX(timeseriesPoints[1]);
for (let i = 0; i < timeseriesPoints.length; i += 2) { for (let i = 0; i < timeseriesPoints.length; i += 2 * this.resolution) {
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.moveTo(Math.round(x), Math.round(y)); this.ctx.moveTo(Math.round(x), Math.round(y));
y = this.getY(timeseriesPoints[i]); y = 0;
x = this.getX(timeseriesPoints[i + 1]); x = 0;
for (let j = 0; j < this.resolution * 2 && (j + 2 < timeseriesPoints.length); j += 2) {
y += timeseriesPoints[i + j];
x += timeseriesPoints[i + 1 + j];
}
y = this.getY(y / this.resolution, scale);
x = this.getX(x / this.resolution);
this.ctx.lineTo(Math.round(x), Math.round(y)); this.ctx.lineTo(Math.round(x), Math.round(y));
this.ctx.stroke(); this.ctx.stroke();
if (drawBubbles) { if (this.resolution === 1) {
this.ctx.beginPath(); this.ctx.beginPath();
this.ctx.ellipse(x, y, 2, 2, 0, 0, 2 * Math.PI); this.ctx.ellipse(x, y, 2, 2, 0, 0, 2 * Math.PI);
this.ctx.stroke(); this.ctx.stroke();

View File

@@ -1,4 +1,5 @@
import Timeseries from "./Timeseries"; import Timeseries from "./Timeseries";
import {ScaleId} from "./ClimateChart";
export class AppStateError extends Error { export class AppStateError extends Error {
constructor(message: string) { constructor(message: string) {
@@ -11,7 +12,7 @@ export type DisplayMode = "window" | "pastMins";
export interface EventCallback { export interface EventCallback {
newTimeseries: (timeseries: Timeseries) => void; newTimeseries: (timeseries: Timeseries) => void;
timeseriesUpdated: (timeseries: Timeseries) => void; timeseriesUpdated: (timeseries: Timeseries, scale?: ScaleId) => void;
} }
type EventCallbackListing<K extends keyof EventCallback> = Record<K, EventCallback[K][]>; type EventCallbackListing<K extends keyof EventCallback> = Record<K, EventCallback[K][]>;
@@ -25,7 +26,8 @@ interface AppState {
displayWindow: TimeWindow; displayWindow: TimeWindow;
minutesDisplayed: number; minutesDisplayed: number;
utcOffset: number; utcOffset: number;
timeseries: Timeseries[], leftTimeseries: Timeseries[],
rightTimeseries: Timeseries[],
overlayText: string; overlayText: string;
dataEndpointBase: string; dataEndpointBase: string;
updateIntervalSeconds: number; updateIntervalSeconds: number;
@@ -63,13 +65,18 @@ class AppStateStore {
await this.getNewTimeseriesData(); await this.getNewTimeseriesData();
} }
addTimeseries(timeseries: Timeseries) { addTimeseries(timeseries: Timeseries, scale?: ScaleId) {
if (this.state.timeseries.indexOf(timeseries) >= 0) { const group = scale === ScaleId.Left ? this.state.leftTimeseries : this.state.rightTimeseries;
if (group.indexOf(timeseries) >= 0) {
throw new AppStateError("Timeseries has already been added!"); throw new AppStateError("Timeseries has already been added!");
} }
this.state.timeseries.push(timeseries); if (scale === ScaleId.Left) {
this.notifyStoreVal("timeseries"); group.push(timeseries);
this.eventCallbacks["newTimeseries"].forEach(cb => cb(timeseries)); } else {
group.push(timeseries);
}
this.notifyStoreVal(scale === ScaleId.Left ? "leftTimeseries" : "rightTimeseries");
this.eventCallbacks["newTimeseries"].forEach(cb => cb(timeseries, scale));
this.updateTimeseriesFromSettings(); this.updateTimeseriesFromSettings();
} }
@@ -90,29 +97,39 @@ class AppStateStore {
stop = this.state.lastUpdateTime; stop = this.state.lastUpdateTime;
} }
this.addLoad(); this.addLoad();
console.log(start, stop); for (const timeseries of this.state.leftTimeseries) {
for (const timeseries of this.state.timeseries) { await timeseries.updateFromWindow(start, stop);
}
for (const timeseries of this.state.rightTimeseries) {
await timeseries.updateFromWindow(start, stop); await timeseries.updateFromWindow(start, stop);
} }
this.finishLoad(); this.finishLoad();
for (const timeseries of this.state.timeseries) { this.notifyAllTimeseriesUpdated();
this.notifyStoreVal("timeseries");
this.eventCallbacks["timeseriesUpdated"].forEach(cb => cb(timeseries));
}
} }
private async getNewTimeseriesData() { private async getNewTimeseriesData() {
const updateTime = new Date().getTime() / 1000; const updateTime = new Date().getTime() / 1000;
this.addLoad(); this.addLoad();
for (const timeseries of this.state.timeseries) { for (const timeseries of this.state.leftTimeseries) {
await timeseries.getLatest();
}
for (const timeseries of this.state.rightTimeseries) {
await timeseries.getLatest(); await timeseries.getLatest();
} }
this.finishLoad(); this.finishLoad();
for (const timeseries of this.state.timeseries) { this.setLastUpdateTime(updateTime);
this.notifyStoreVal("timeseries"); this.notifyAllTimeseriesUpdated();
}
private notifyAllTimeseriesUpdated() {
for (const timeseries of this.state.leftTimeseries) {
this.notifyStoreVal("leftTimeseries");
this.eventCallbacks["timeseriesUpdated"].forEach(cb => cb(timeseries));
}
for (const timeseries of this.state.rightTimeseries) {
this.notifyStoreVal("rightTimeseries");
this.eventCallbacks["timeseriesUpdated"].forEach(cb => cb(timeseries)); this.eventCallbacks["timeseriesUpdated"].forEach(cb => cb(timeseries));
} }
this.setLastUpdateTime(updateTime);
} }
getState(): AppState { getState(): AppState {
@@ -134,11 +151,13 @@ class AppStateStore {
setDisplayWindow(newWin: TimeWindow) { setDisplayWindow(newWin: TimeWindow) {
if (newWin.start < newWin.stop) { if (newWin.start < newWin.stop) {
if (newWin.stop < this.state.lastUpdateTime) {
this.state.displayWindow = {...newWin}; this.state.displayWindow = {...newWin};
this.notifyStoreVal("displayWindow"); this.notifyStoreVal("displayWindow");
this.updateTimeseriesFromSettings(); this.updateTimeseriesFromSettings();
}
} else { } else {
throw new AppStateError(`Invalid display window from ${newWin.start} to ${newWin.stop}`); console.warn(`Invalid display window from ${newWin.start} to ${newWin.stop}`);
} }
} }

View File

@@ -27,7 +27,7 @@ class Timeseries {
this.loader = loader; this.loader = loader;
this.name = name; this.name = name;
this.tolerance = tolerance ?? 0; this.tolerance = tolerance ?? 0;
this.colour = `rgb(${Math.random() * 255},${Math.random() * 255},${Math.random() * 255})`; this.colour = `rgb(${Math.random() * 150},${Math.random() * 150},${Math.random() * 150})`;
} }
getExtrema(): Extrema { getExtrema(): Extrema {
@@ -51,7 +51,7 @@ class Timeseries {
return new Int32Array(); return new Int32Array();
} else { } else {
return this.cache.slice( return this.cache.slice(
this.findIndexInCache(start), this.findIndexInCache(start) - 1,
this.findIndexInCache(stop) this.findIndexInCache(stop)
); );
} }
@@ -164,9 +164,13 @@ class Timeseries {
} }
private findIndexInCache(soughtIndex: number) { private findIndexInCache(soughtIndex: number) {
return this.findIndexInCacheBinary(soughtIndex);
}
private findIndexInCacheLinear(soughtIndex: number) {
for (let i = 1; i < this.cache.length; i += 2) { for (let i = 1; i < this.cache.length; i += 2) {
if (soughtIndex < this.cache[i]) { if (soughtIndex < this.cache[i]) {
return i - 1; return i > 3 ? i - 3 : i - 1;
} }
} }
return this.cache.length - 2; return this.cache.length - 2;
@@ -174,7 +178,7 @@ class Timeseries {
private findIndexInCacheBinary(soughtIndex: number, listStart = 0, listStop: number = (this.currentEndPointer / 2)): number { private findIndexInCacheBinary(soughtIndex: number, listStart = 0, listStop: number = (this.currentEndPointer / 2)): number {
if (listStop - listStart === 1) { if (listStop - listStart === 1) {
return listStart; return listStart * 2 + 1;
} else { } else {
const middle = Math.floor((listStop + listStart) / 2); const middle = Math.floor((listStop + listStart) / 2);
const val = this.cache[middle * 2 + 1]; const val = this.cache[middle * 2 + 1];
@@ -183,7 +187,7 @@ class Timeseries {
} else if (val < soughtIndex) { } else if (val < soughtIndex) {
return this.findIndexInCacheBinary(soughtIndex, middle, listStop); return this.findIndexInCacheBinary(soughtIndex, middle, listStop);
} else { } else {
return middle; return middle * 2 + 1;
} }
} }
} }

View File

@@ -1,97 +0,0 @@
import {ChartConfiguration, ChartPoint, TimeUnit} from "chart.js";
interface ClimateChartSettings {
humidity?: ChartPoint[];
temp?: ChartPoint[];
co2?: ChartPoint[];
colors?: {
humidity?: string;
temp?: string;
co2?: string;
}
}
const defaultHumidityColor = "rgb(196,107,107)";
const defaultTempColor = "rgb(173,136,68)";
const defaultCo2Color = "rgb(52,133,141)";
export function generateClimateChartConfig(settings: ClimateChartSettings): ChartConfiguration {
return {
type: "line",
data: {
datasets: [{
label: "Humidity",
data: settings.humidity,
borderColor: settings.colors?.humidity ?? defaultHumidityColor,
fill: false,
yAxisID: "y-axis-3",
}, {
label: "Temperature",
data: settings.temp,
borderColor: settings.colors?.temp ?? defaultTempColor,
fill: false,
yAxisID: "y-axis-2",
}, {
label: "Co2 Concentration",
data: settings.co2,
borderColor: settings.colors?.co2 ?? defaultCo2Color,
fill: false,
yAxisID: "y-axis-1",
}]
},
options: {
animation: {animateRotate: false, duration: 0, animateScale: false},
responsive: true,
maintainAspectRatio: false,
legend: {
position: "top",
align: "end",
},
scales: {
xAxes: [{
type: "time",
time: {
unit: "second" as TimeUnit
}
}],
yAxes: [{
type: "linear",
display: true,
position: "right",
id: "y-axis-1",
ticks: {
fontColor: settings.colors?.co2 ?? defaultCo2Color,
suggestedMin: 400,
suggestedMax: 1100,
},
}, {
type: "linear",
display: true,
position: "left",
id: "y-axis-2",
ticks: {
fontColor: settings.colors?.temp ?? defaultTempColor,
suggestedMin: 10,
suggestedMax: 35,
},
gridLines: {
drawOnChartArea: false,
},
}, {
type: "linear",
display: true,
position: "left",
id: "y-axis-3",
ticks: {
fontColor: settings.colors?.humidity ?? defaultHumidityColor,
suggestedMin: 15,
suggestedMax: 85,
},
gridLines: {
drawOnChartArea: false,
},
}],
}
},
};
}

View File

@@ -3,6 +3,8 @@ import {AppStore, getAppState, initStore} from "./StateStore";
import AppUI from "./ui-components/AppUI"; import AppUI from "./ui-components/AppUI";
import Timeseries from "./Timeseries"; import Timeseries from "./Timeseries";
import {ClayPIDashboardError} from "./errors"; import {ClayPIDashboardError} from "./errors";
import {ScaleId} from "./ClimateChart";
export {config}; export {config};
function getDisplayedMinutes() { function getDisplayedMinutes() {
@@ -35,23 +37,32 @@ async function init() {
fatalError: null, fatalError: null,
displayWindow: {start: now - getDisplayedMinutes() * 60, stop: now}, displayWindow: {start: now - getDisplayedMinutes() * 60, stop: now},
documentReady: false, documentReady: false,
timeseries: [], leftTimeseries: [],
rightTimeseries: [],
}); });
AppStore().addTimeseries(new Timeseries( AppStore().addTimeseries(
new Timeseries(
"temp", "temp",
(start, stop) => loadClimateTimeseriesData("temp", start, stop), (start, stop) => loadClimateTimeseriesData("temp", start, stop),
getAppState().updateIntervalSeconds getAppState().updateIntervalSeconds
)); ),
AppStore().addTimeseries(new Timeseries( ScaleId.Left);
AppStore().addTimeseries(
new Timeseries(
"humidity", "humidity",
(start, stop) => loadClimateTimeseriesData("humidity", start, stop), (start, stop) => loadClimateTimeseriesData("humidity", start, stop),
getAppState().updateIntervalSeconds getAppState().updateIntervalSeconds
)); ),
AppStore().addTimeseries(new Timeseries( ScaleId.Left
);
AppStore().addTimeseries(
new Timeseries(
"co2", "co2",
(start, stop) => loadClimateTimeseriesData("co2", start, stop), (start, stop) => loadClimateTimeseriesData("co2", start, stop),
getAppState().updateIntervalSeconds getAppState().updateIntervalSeconds
)); ),
ScaleId.Right
);
const ui = new AppUI(); const ui = new AppUI();
ui.bootstrap("root"); ui.bootstrap("root");
} }

View File

@@ -1,7 +1,7 @@
import {AppStore, DisplayMode, getAppState} from "../StateStore"; import {AppStore, DisplayMode, getAppState} from "../StateStore";
import GridWidget, {GridProps} from "./GridWidget"; import GridWidget, {GridProps} from "./GridWidget";
import UIComponent from "./UIComponent"; import UIComponent from "./UIComponent";
import ClimateChart from "../ClimateChart"; import ClimateChart, {ScaleId} from "../ClimateChart";
class ClimateChartWidget extends UIComponent { class ClimateChartWidget extends UIComponent {
private readonly skeleton: GridWidget; private readonly skeleton: GridWidget;
@@ -54,9 +54,8 @@ class ClimateChartWidget extends UIComponent {
AppStore().addLoad(); AppStore().addLoad();
const ctx = this.canvasElement.getContext("2d", {alpha: false}); const ctx = this.canvasElement.getContext("2d", {alpha: false});
this.chart = new ClimateChart(ctx); this.chart = new ClimateChart(ctx);
for (const timeseries of getAppState().timeseries) { getAppState().leftTimeseries.forEach(timeseries => this.chart.addTimeseries(timeseries, ScaleId.Left));
this.chart.addTimeseries(timeseries); getAppState().rightTimeseries.forEach(timeseries => this.chart.addTimeseries(timeseries, ScaleId.Right));
}
await this.rerender(); await this.rerender();
this.initialised = true; this.initialised = true;
} catch (e) { } catch (e) {