1
+
Added:
import { TIMEFRAMES } from "./constants.js";
2
+
Added:
import { formatDateTime } from "./format.js";
3
+
Added:
import { createTimeframeState } from "./timeframe.js";
4
+
Added:
import { initialiseGraphControls } from "./graph-controls.js";
5
+
Added:
6
+
Added:
const TOOLTIP_DEFAULTS = {
7
+
Added:
mode: "index",
8
+
Added:
intersect: false,
9
+
Added:
backgroundColor: "rgba(30, 41, 59, 0.7)",
10
+
Added:
titleFont: { size: 11, weight: "normal" },
11
+
Added:
bodyFont: { size: 12 },
12
+
Added:
padding: { x: 8, y: 5 },
13
+
Added:
cornerRadius: 4,
14
+
Added:
caretSize: 0
15
+
Added:
};
16
+
Added:
17
+
Added:
let charts = {};
18
+
Added:
19
+
Added:
async function fetchJson(url) {
20
+
Added:
const response = await fetch(url);
21
+
Added:
if (!response.ok) throw new Error(`HTTP ${response.status}`);
22
+
Added:
return response.json();
23
+
Added:
}
24
+
Added:
25
+
Added:
function timeframeToHours(timeframe) {
26
+
Added:
const tf = timeframe.selected();
27
+
Added:
const range = timeframe.range();
28
+
Added:
const ms = TIMEFRAMES[tf]?.ms || TIMEFRAMES.day.ms;
29
+
Added:
return Math.round((ms * range) / (60 * 60 * 1000));
30
+
Added:
}
31
+
Added:
32
+
Added:
async function loadTemperature(timeframe, signal) {
33
+
Added:
const canvas = document.getElementById("chart-weather-temperature");
34
+
Added:
if (!canvas) return;
35
+
Added:
36
+
Added:
const hours = timeframeToHours(timeframe);
37
+
Added:
const data = await fetchJson(`/api/weather/hourly?hours=${hours}`);
38
+
Added:
const labels = data.points.map(p => formatDateTime(p.timestamp));
39
+
Added:
const values = data.points.map(p => p.temperature);
40
+
Added:
41
+
Added:
if (!charts.temperature) {
42
+
Added:
charts.temperature = new Chart(canvas, {
43
+
Added:
type: "line",
44
+
Added:
data: {
45
+
Added:
labels,
46
+
Added:
datasets: [{
47
+
Added:
label: "Temperature (°C)",
48
+
Added:
data: values,
49
+
Added:
borderColor: "#dc2626",
50
+
Added:
backgroundColor: "rgba(220, 38, 38, 0.08)",
51
+
Added:
tension: 0.3,
52
+
Added:
pointRadius: 0,
53
+
Added:
pointHoverRadius: 3,
54
+
Added:
fill: true
55
+
Added:
}]
56
+
Added:
},
57
+
Added:
options: {
58
+
Added:
responsive: true,
59
+
Added:
maintainAspectRatio: false,
60
+
Added:
animation: false,
61
+
Added:
scales: {
62
+
Added:
x: { ticks: { maxTicksLimit: 8, font: { size: 10 } } },
63
+
Added:
y: { title: { display: true, text: "°C" } }
64
+
Added:
},
65
+
Added:
plugins: {
66
+
Added:
legend: { display: false },
67
+
Added:
tooltip: { ...TOOLTIP_DEFAULTS, callbacks: { title: () => "" } }
68
+
Added:
}
69
+
Added:
}
70
+
Added:
});
71
+
Added:
} else {
72
+
Added:
charts.temperature.data.labels = labels;
73
+
Added:
charts.temperature.data.datasets[0].data = values;
74
+
Added:
charts.temperature.update();
75
+
Added:
}
76
+
Added:
}
77
+
Added:
78
+
Added:
async function loadHumidity(timeframe, signal) {
79
+
Added:
const canvas = document.getElementById("chart-weather-humidity");
80
+
Added:
if (!canvas) return;
81
+
Added:
82
+
Added:
const hours = timeframeToHours(timeframe);
83
+
Added:
const data = await fetchJson(`/api/weather/hourly?hours=${hours}`);
84
+
Added:
const labels = data.points.map(p => formatDateTime(p.timestamp));
85
+
Added:
const values = data.points.map(p => p.humidity);
86
+
Added:
87
+
Added:
if (!charts.humidity) {
88
+
Added:
charts.humidity = new Chart(canvas, {
89
+
Added:
type: "line",
90
+
Added:
data: {
91
+
Added:
labels,
92
+
Added:
datasets: [{
93
+
Added:
label: "Humidity (%)",
94
+
Added:
data: values,
95
+
Added:
borderColor: "#0ea5e9",
96
+
Added:
backgroundColor: "rgba(14, 165, 233, 0.08)",
97
+
Added:
tension: 0.3,
98
+
Added:
pointRadius: 0,
99
+
Added:
pointHoverRadius: 3,
100
+
Added:
fill: true
101
+
Added:
}]
102
+
Added:
},
103
+
Added:
options: {
104
+
Added:
responsive: true,
105
+
Added:
maintainAspectRatio: false,
106
+
Added:
animation: false,
107
+
Added:
scales: {
108
+
Added:
x: { ticks: { maxTicksLimit: 8, font: { size: 10 } } },
109
+
Added:
y: { title: { display: true, text: "%" }, min: 0, max: 100 }
110
+
Added:
},
111
+
Added:
plugins: {
112
+
Added:
legend: { display: false },
113
+
Added:
tooltip: { ...TOOLTIP_DEFAULTS, callbacks: { title: () => "" } }
114
+
Added:
}
115
+
Added:
}
116
+
Added:
});
117
+
Added:
} else {
118
+
Added:
charts.humidity.data.labels = labels;
119
+
Added:
charts.humidity.data.datasets[0].data = values;
120
+
Added:
charts.humidity.update();
121
+
Added:
}
122
+
Added:
}
123
+
Added:
124
+
Added:
async function loadRain(timeframe, signal) {
125
+
Added:
const canvas = document.getElementById("chart-weather-rain");
126
+
Added:
if (!canvas) return;
127
+
Added:
128
+
Added:
const hours = timeframeToHours(timeframe);
129
+
Added:
const data = await fetchJson(`/api/weather/hourly?hours=${hours}`);
130
+
Added:
const labels = data.points.map(p => formatDateTime(p.timestamp));
131
+
Added:
const values = data.points.map(p => p.rain || 0);
132
+
Added:
133
+
Added:
if (!charts.rain) {
134
+
Added:
charts.rain = new Chart(canvas, {
135
+
Added:
type: "bar",
136
+
Added:
data: {
137
+
Added:
labels,
138
+
Added:
datasets: [{
139
+
Added:
label: "Rain (mm)",
140
+
Added:
data: values,
141
+
Added:
backgroundColor: "rgba(14, 165, 233, 0.5)",
142
+
Added:
borderColor: "#0ea5e9",
143
+
Added:
borderWidth: 1
144
+
Added:
}]
145
+
Added:
},
146
+
Added:
options: {
147
+
Added:
responsive: true,
148
+
Added:
maintainAspectRatio: false,
149
+
Added:
animation: false,
150
+
Added:
scales: {
151
+
Added:
x: { ticks: { maxTicksLimit: 8, font: { size: 10 } } },
152
+
Added:
y: { beginAtZero: true, title: { display: true, text: "mm" } }
153
+
Added:
},
154
+
Added:
plugins: {
155
+
Added:
legend: { display: false },
156
+
Added:
tooltip: { ...TOOLTIP_DEFAULTS, callbacks: { title: () => "" } }
157
+
Added:
}
158
+
Added:
}
159
+
Added:
});
160
+
Added:
} else {
161
+
Added:
charts.rain.data.labels = labels;
162
+
Added:
charts.rain.data.datasets[0].data = values;
163
+
Added:
charts.rain.update();
164
+
Added:
}
165
+
Added:
}
166
+
Added:
167
+
Added:
export function createWeatherCharts() {
168
+
Added:
const hasCanvas = document.getElementById("chart-weather-temperature");
169
+
Added:
if (!hasCanvas) return { reload: () => {} };
170
+
Added:
171
+
Added:
const timeframe = createTimeframeState();
172
+
Added:
let controller = null;
173
+
Added:
174
+
Added:
function loadAll() {
175
+
Added:
if (controller) controller.abort();
176
+
Added:
controller = new AbortController();
177
+
Added:
const signal = controller.signal;
178
+
Added:
179
+
Added:
return Promise.allSettled([
180
+
Added:
loadTemperature(timeframe, signal),
181
+
Added:
loadHumidity(timeframe, signal),
182
+
Added:
loadRain(timeframe, signal)
183
+
Added:
]);
184
+
Added:
}
185
+
Added:
186
+
Added:
initialiseGraphControls(timeframe, () => loadAll());
187
+
Added:
loadAll();
188
+
Added:
189
+
Added:
return {
190
+
Added:
reload() { loadAll(); }
191
+
Added:
};
192
+
Added:
}