Datum/Zeit auf der X-Achse berichtigt

This commit is contained in:
2026-05-17 10:07:53 +02:00
parent d579cd5ac6
commit 0a3e0fc2c1
4 changed files with 69 additions and 39 deletions
+3 -2
View File
@@ -2,7 +2,7 @@ FROM node:20-alpine AS builder
WORKDIR /app WORKDIR /app
RUN apk add --no-cache python3 make g++ RUN apk add --no-cache python3 make g++
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm install
COPY . . COPY . .
RUN npm run build RUN npm run build
@@ -12,7 +12,8 @@ ENV NODE_ENV=production
RUN apk add --no-cache python3 make g++ RUN apk add --no-cache python3 make g++
COPY package*.json ./ COPY package*.json ./
RUN npm ci --omit=dev COPY --from=builder /app/node_modules ./node_modules
RUN npm prune --omit=dev
COPY --from=builder /app/build ./build COPY --from=builder /app/build ./build
RUN mkdir -p data RUN mkdir -p data
+26 -27
View File
@@ -1,15 +1,17 @@
{ {
"name": "bodenfeuchte", "name": "bodenfeuchte",
"version": "0.1.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "bodenfeuchte", "name": "bodenfeuchte",
"version": "0.1.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"better-sqlite3": "^12.10.0", "better-sqlite3": "^12.10.0",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",
"chartjs-adapter-date-fns": "^3.0.0",
"date-fns": "^4.1.0",
"mqtt": "^5.15.1" "mqtt": "^5.15.1"
}, },
"devDependencies": { "devDependencies": {
@@ -32,31 +34,6 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": { "node_modules/@emnapi/wasi-threads": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
@@ -1191,6 +1168,7 @@
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@kurkle/color": "^0.3.0" "@kurkle/color": "^0.3.0"
}, },
@@ -1198,6 +1176,16 @@
"pnpm": ">=8" "pnpm": ">=8"
} }
}, },
"node_modules/chartjs-adapter-date-fns": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz",
"integrity": "sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==",
"license": "MIT",
"peerDependencies": {
"chart.js": ">=2.8.0",
"date-fns": ">=2.0.0"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
@@ -1282,6 +1270,17 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/date-fns": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
"license": "MIT",
"peer": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+3 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "bodenfeuchte", "name": "bodenfeuchte",
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
@@ -14,6 +14,8 @@
"dependencies": { "dependencies": {
"better-sqlite3": "^12.10.0", "better-sqlite3": "^12.10.0",
"chart.js": "^4.5.0", "chart.js": "^4.5.0",
"chartjs-adapter-date-fns": "^3.0.0",
"date-fns": "^4.1.0",
"mqtt": "^5.15.1" "mqtt": "^5.15.1"
}, },
"devDependencies": { "devDependencies": {
+37 -9
View File
@@ -2,7 +2,9 @@
import { base } from '$app/paths'; import { base } from '$app/paths';
import { onMount, onDestroy } from 'svelte'; import { onMount, onDestroy } from 'svelte';
import 'chart.js/auto'; import 'chart.js/auto';
import 'chartjs-adapter-date-fns';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import { de } from 'date-fns/locale';
type Measurement = { timestamp: number; soil_moisture: number }; type Measurement = { timestamp: number; soil_moisture: number };
@@ -21,10 +23,10 @@
function updateChart() { function updateChart() {
if (!chart) return; if (!chart) return;
chart.data.labels = data.map(d => const now = Date.now();
new Date(d.timestamp).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) chart.data.datasets[0].data = data.map(d => ({ x: d.timestamp, y: d.soil_moisture }));
); chart.options.scales!['x']!.min = now - 6 * 60 * 60 * 1000;
chart.data.datasets[0].data = data.map(d => d.soil_moisture); chart.options.scales!['x']!.max = now;
chart.update(); chart.update();
} }
@@ -32,14 +34,13 @@
chart = new Chart(canvas, { chart = new Chart(canvas, {
type: 'line', type: 'line',
data: { data: {
labels: [],
datasets: [{ datasets: [{
label: 'Bodenfeuchte (%)', label: 'Bodenfeuchte (%)',
data: [], data: [] as {x: number, y: number}[],
borderColor: '#2980b9', borderColor: '#2980b9',
backgroundColor: 'rgba(133, 183, 215, 0.2)', backgroundColor: 'rgba(133, 183, 215, 0.2)',
borderWidth: 2, borderWidth: 2,
pointRadius: 0, pointRadius: 3,
pointHoverRadius: 4, pointHoverRadius: 4,
fill: true, fill: true,
tension: 0.3, tension: 0.3,
@@ -50,8 +51,35 @@
maintainAspectRatio: false, maintainAspectRatio: false,
scales: { scales: {
x: { x: {
ticks: { color: '#555', maxTicksLimit: 8 }, type: 'time',
grid: { color: '#e5e7eb' }, min: Date.now() - 6 * 60 * 60 * 1000,
max: Date.now(),
time: {
unit: 'minute',
displayFormats: { minute: 'HH:mm', hour: 'HH:mm' },
},
adapters: { date: { locale: de } },
afterBuildTicks(scale: any) {
// Rebuild ticks at exactly 10-minute intervals
const min = scale.min;
const max = scale.max;
const step = 10 * 60 * 1000;
const start = Math.ceil(min / step) * step;
const ticks = [];
for (let t = start; t <= max; t += step) {
const d = new Date(t);
ticks.push({ value: t, major: d.getMinutes() === 0 });
}
scale.ticks = ticks;
},
ticks: {
major: { enabled: true },
autoSkip: false,
color: (ctx: any) => ctx.tick?.major ? '#222' : '#aaa',
font: (ctx: any) => ctx.tick?.major ? { weight: 'bold' as const, size: 13 } : { size: 10 },
maxRotation: 0,
},
grid: { color: (ctx: any) => ctx.tick?.major ? '#999' : '#e5e7eb', lineWidth: 1 },
}, },
y: { y: {
ticks: { color: '#555', callback: v => `${v} %` }, ticks: { color: '#555', callback: v => `${v} %` },