Add data plot to sauna route

Signed-off-by: Thomas Klaehn <thomas.klaehn@perinet.io>
This commit is contained in:
Thomas Klaehn 2024-07-03 11:25:54 +02:00
parent 59cb6d1380
commit 5822328d9d
4 changed files with 648 additions and 393 deletions

914
webui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@
},
"type": "module",
"dependencies": {
"hamburger-menu-svelte": "^2.2.0"
"hamburger-menu-svelte": "^2.2.0",
"uplot": "^1.6.30"
}
}

113
webui/src/lib/Uplot.svelte Normal file
View File

@ -0,0 +1,113 @@
<script lang="ts">
import 'uplot/dist/uPlot.min.css';
export let data_label = ""; // data label in legend
export let unit = ""; // unit in legend
export let last_for_seconds = 20; // Period of time [s] the data are shifted out on the left
let plotContainer;
let data = [];
data[0] = [Date.now()];
data[1] = [null];
const PRECISION = 1;
export async function create(legend_label, legend_unit, duration) {
console.log(legend_label);
console.log(legend_unit);
console.log(duration);
data_label = legend_label;
unit = legend_unit;
last_for_seconds = duration;
const uplotModule = await import('uplot');
const uPlot = uplotModule.default;
redraw(uPlot);
}
export async function add_data(new_data) {
data[0].push(Date.now());
data[1].push(new_data);
}
function redraw(uPlot) {
function getSize() {
let max_height = window.innerHeight;
let max_width = window.innerWidth;
let current_posY = plotContainer.getBoundingClientRect().top;
let height = Math.max(max_height - current_posY - 200, 250);
let width = Math.max(max_width - (2 * 50), 600);
return {
width: width,
height: height,
}
}
const opts = {
...getSize(),
ms: 1,
cursor: {drag: {setScale: false}},
points: {show: false},
pxSnap: false,
pxAlign: 0,
series: [
{
label: "time"
},
{
label: data_label,
stroke: "red",
paths: uPlot.paths.spline(),
value: (u, v, sidx, didx) => {
if (didx == null) {
let d = u.data[sidx];
v = d[d.length - 1];
}
let value = v;
if ( value != null) {
value = value.toFixed(PRECISION) + unit;
}
return value;
},
pxAlign: 0,
points: { show: false },
}
],
axes: [{grid: { show: false}}, {grid: { show: false}}],
// hooks are used here to filter out the time series from the legend
hooks: {
init: [
u => {
[...u.root.querySelectorAll('.u-legend .u-series')].forEach((el, i) => {
if (u.series[i].label ==="time") {
el.style.display = 'none';
}
});
}
]
}
};
let uplot = new uPlot(opts, data, plotContainer);
function update() {
const now = Date.now();
const scale = { min: now - (last_for_seconds * 1000), max: now };
// shift out data earlier than chart origin
let last = Date.now();
let first = last - (last_for_seconds * 1000);
while(data[0][0] < first) {
for(let i = 0; i < data.length; i++) {
data[i].shift();
}
}
uplot.setData(data, false);
uplot.setScale('x', scale);
uplot.setLegend({idx: data[0].length - 1}, false);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
}
</script>
<div bind:this={plotContainer}></div>

View File

@ -1,5 +1,6 @@
<script>
import { onMount } from "../../../node_modules/svelte/internal";
import Plot from "$lib/Uplot.svelte"
import icon from "$lib/images/sauna.svg"
let backend_url = "https://home.blackfinn.de/api/sauna";
@ -8,6 +9,7 @@
let temperature_unit = "°C"
let time = "00:00:00";
let updated = false;
let plot;
function get_temperature() {
fetch(backend_url)
@ -16,6 +18,9 @@
temperature_unit = data.unit
temperature_value = data.value
time = data.time.slice(11, 19)
if(plot) {
plot.add_data(Math.floor(Math.random() * 30) - 15);
}
updated = JSON.parse(data.valid);
}).catch(error => {
console.log(error);
@ -28,6 +33,9 @@
}, 1000)
onMount(async () => {
if(plot) {
plot.create("Temperature", "°C", 4 * 60 * 60 /* 4h */);
}
get_temperature();
});
</script>
@ -45,5 +53,8 @@
{#if updated}
<div>{time} Uhr:</div>
<h1>{temperature_value} {temperature_unit}</h1>
<div><Plot/></div>
{/if}
</section>
<Plot bind:this={plot}/>