@sailingrotevista/rotevista-dash 7.0.45 → 8.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @sailingrotevista/rotevista-dash might be problematic. Click here for more details.
- package/README.md +28 -70
- package/index.js +14 -0
- package/package.json +26 -1
- package/public/assets/dash-DmyxO1aO.png +0 -0
- package/public/assets/index-BFSy7dOW.css +1 -0
- package/public/assets/index-BHhAwNqq.js +9 -0
- package/public/assets/manifest-Ea_0WKQ8.json +17 -0
- package/public/icons/dash.png +0 -0
- package/public/index.html +23 -0
- package/public/manifest.json +17 -0
- package/app.js +0 -1505
- package/charts.js +0 -305
- package/debug.html +0 -1037
- package/gauge.js +0 -90
- package/index.html +0 -264
- package/style.css +0 -640
- package/utils.js +0 -148
- package/weather-radar.js +0 -305
- package/weather_debug.html +0 -181
package/charts.js
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* ==========================================================================
|
|
4
|
-
* Sailing Dashboard Pro - Sparkline Graphics Engine
|
|
5
|
-
* ==========================================================================
|
|
6
|
-
* Gestisce l'adattamento delle scale dei grafici, l'arrotondamento dei limiti
|
|
7
|
-
* (Snap a griglia) e la generazione dinamica delle curve SVG.
|
|
8
|
-
* file charts.js
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// --- 1. MOTORE DI CALCOLO DELLE SCALE (Snap a griglia & Protezione Profondità) ---
|
|
12
|
-
function calculateScale(type, data, mode) {
|
|
13
|
-
const s = CONFIG.scales[type];
|
|
14
|
-
const currentVal = data[data.length - 1];
|
|
15
|
-
|
|
16
|
-
if (currentVal === undefined || currentVal === null) {
|
|
17
|
-
return { min: 0, max: s ? s.stdMax : 10 };
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (!store.herculesScales) store.herculesScales = {};
|
|
21
|
-
if (!store.herculesScales[type]) {
|
|
22
|
-
// Inizializzazione a NULL dello stato di partenza per consentire un allineamento istantaneo
|
|
23
|
-
// e perfettamente centrato al primo doppio clic
|
|
24
|
-
store.herculesScales[type] = { min: null, max: null };
|
|
25
|
-
}
|
|
26
|
-
let currentScale = store.herculesScales[type];
|
|
27
|
-
|
|
28
|
-
// --- SEZIONE PROFONDITÀ ---
|
|
29
|
-
if (type === 'depth') {
|
|
30
|
-
const shallowThreshold = Math.max(s.stdMax, 10);
|
|
31
|
-
if (store.depthProtectedActive === undefined) store.depthProtectedActive = false;
|
|
32
|
-
|
|
33
|
-
const now = Date.now();
|
|
34
|
-
const depthSafetyWindowMs = 120000; // 2 minuti
|
|
35
|
-
|
|
36
|
-
const recentPoints = store.histories.depth.filter(p => (now - p.time) <= depthSafetyWindowMs);
|
|
37
|
-
const recentVals = recentPoints.map(p => p.val);
|
|
38
|
-
|
|
39
|
-
const localMax = recentVals.length > 0 ? Math.max(...recentVals) : currentVal;
|
|
40
|
-
const localMin = recentVals.length > 0 ? Math.min(...recentVals) : currentVal;
|
|
41
|
-
|
|
42
|
-
if (mode !== 'hercules') {
|
|
43
|
-
if (!store.depthProtectedActive && currentVal <= shallowThreshold) {
|
|
44
|
-
store.depthProtectedActive = true;
|
|
45
|
-
}
|
|
46
|
-
if (store.depthProtectedActive) {
|
|
47
|
-
if (localMin > shallowThreshold) {
|
|
48
|
-
store.depthProtectedActive = false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (store.depthProtectedActive) {
|
|
52
|
-
return { min: 0, max: shallowThreshold };
|
|
53
|
-
}
|
|
54
|
-
const maxHistorico = Math.max(...data);
|
|
55
|
-
return { min: 0, max: Math.max(s.stdMax, Math.ceil(maxHistorico / s.step) * s.step) };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (mode === 'hercules') {
|
|
59
|
-
const roundStep = s.hercSpan;
|
|
60
|
-
let targetMin = 0;
|
|
61
|
-
if (localMin > shallowThreshold) {
|
|
62
|
-
targetMin = Math.max(0, Math.floor(localMin / roundStep) * roundStep);
|
|
63
|
-
}
|
|
64
|
-
let targetMax = Math.ceil(localMax / roundStep) * roundStep;
|
|
65
|
-
|
|
66
|
-
if (targetMax - targetMin < 4) {
|
|
67
|
-
targetMax = targetMin + 4;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Gestione dell'allineamento iniziale o dello scostamento dei limiti della scala
|
|
71
|
-
if (currentScale.min === null || currentVal < currentScale.min || currentVal > currentScale.max) {
|
|
72
|
-
currentScale.min = currentScale.min === null ? targetMin : Math.min(currentScale.min, targetMin);
|
|
73
|
-
currentScale.max = currentScale.max === null ? targetMax : Math.max(currentScale.max, targetMax);
|
|
74
|
-
} else {
|
|
75
|
-
const allStableInTarget = recentVals.every(val => val >= targetMin && val <= targetMax);
|
|
76
|
-
if (allStableInTarget) {
|
|
77
|
-
currentScale.min = targetMin;
|
|
78
|
-
currentScale.max = targetMax;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return { min: currentScale.min, max: currentScale.max };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// --- ALTRI GRAFICI (STW, SOG, TWS) ---
|
|
86
|
-
if (mode !== 'hercules') {
|
|
87
|
-
const maxHistorico = Math.max(...data);
|
|
88
|
-
return { min: 0, max: Math.max(s.stdMax, Math.ceil(maxHistorico / s.step) * s.step) };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (mode === 'hercules') {
|
|
92
|
-
const roundStep = s.hercSpan;
|
|
93
|
-
// Finestra di calcolo ad alta risoluzione: ancorata rigorosamente all'ultimo terzo dei dati
|
|
94
|
-
const oneThirdCount = Math.max(1, Math.floor(data.length / 3));
|
|
95
|
-
const recentThirdData = data.slice(-oneThirdCount);
|
|
96
|
-
|
|
97
|
-
let localMin = Math.min(...recentThirdData);
|
|
98
|
-
let localMax = Math.max(...recentThirdData);
|
|
99
|
-
|
|
100
|
-
let targetMin = Math.max(0, Math.floor(localMin / roundStep) * roundStep);
|
|
101
|
-
let targetMax = Math.ceil(localMax / roundStep) * roundStep;
|
|
102
|
-
|
|
103
|
-
if (targetMax - targetMin === 0) {
|
|
104
|
-
targetMax = targetMin + roundStep;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Gestione dell'allineamento iniziale o dello scostamento dei limiti della scala
|
|
108
|
-
if (currentScale.min === null || currentVal < currentScale.min || currentVal > currentScale.max) {
|
|
109
|
-
currentScale.min = currentScale.min === null ? targetMin : Math.min(currentScale.min, targetMin);
|
|
110
|
-
currentScale.max = currentScale.max === null ? targetMax : Math.max(currentScale.max, targetMax);
|
|
111
|
-
} else {
|
|
112
|
-
const allStableInTarget = recentThirdData.every(val => val >= targetMin && val <= targetMax);
|
|
113
|
-
if (allStableInTarget) {
|
|
114
|
-
currentScale.min = targetMin;
|
|
115
|
-
currentScale.max = targetMax;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return { min: currentScale.min, max: currentScale.max };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Scrive le etichette delle scale numeriche nei box
|
|
123
|
-
function updateScaleLabels(t, min, max) {
|
|
124
|
-
const el = document.getElementById(t + '-scale');
|
|
125
|
-
if (el) el.innerHTML = `<span>${Math.round(max)}</span><span>${Math.round((min+max)/2)}</span><span>${Math.round(min)}</span>`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Intercetta e instrada il rinfresco dei grafici
|
|
129
|
-
function refreshGraph(t) {
|
|
130
|
-
if (t === 'aws') {
|
|
131
|
-
if (displayModeTws !== 'AWS') return;
|
|
132
|
-
t = 'tws';
|
|
133
|
-
}
|
|
134
|
-
if (t === 'vmg') {
|
|
135
|
-
if (displayModeSog !== 'VMG') return;
|
|
136
|
-
t = 'sog';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const boxType = t;
|
|
140
|
-
let rawData;
|
|
141
|
-
|
|
142
|
-
if (t === 'tws' && displayModeTws === 'AWS') {
|
|
143
|
-
rawData = store.histories['aws'];
|
|
144
|
-
} else if (t === 'sog' && displayModeSog === 'VMG') {
|
|
145
|
-
rawData = store.histories['vmg'];
|
|
146
|
-
} else {
|
|
147
|
-
rawData = store.histories[t];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!rawData || rawData.length < 2) return;
|
|
151
|
-
|
|
152
|
-
// ALINEAMENTO STRUTTURALE: Filtriamo lo storico mantenendo solo la finestra temporale visibile
|
|
153
|
-
// prima di calcolare la scala, allineando millimetricamente la scala Y con i pixel disegnati a schermo.
|
|
154
|
-
const now = Date.now();
|
|
155
|
-
const visibleMinutes = CONFIG.graphs.historyMinutes * (isNavigating ? 1 : 2);
|
|
156
|
-
const viewportMs = visibleMinutes * 60000;
|
|
157
|
-
const viewportStart = now - viewportMs;
|
|
158
|
-
|
|
159
|
-
const visibleData = rawData.filter(p => p.time >= viewportStart);
|
|
160
|
-
if (visibleData.length < 2) return; // Abortisce se non ci sono abbastanza punti visibili
|
|
161
|
-
|
|
162
|
-
const values = visibleData.map(p => p.val); // Estrae i valori solo dei punti visibili
|
|
163
|
-
const mode = graphModes[boxType];
|
|
164
|
-
const cfg = calculateScale(boxType, values, mode); // Calcola la scala Y esatta per la viewBox corrente
|
|
165
|
-
|
|
166
|
-
const box = document.querySelector(`.box-${boxType}`);
|
|
167
|
-
if (box) box.classList.toggle('box-hercules', mode === 'hercules');
|
|
168
|
-
|
|
169
|
-
updateScaleLabels(boxType, cfg.min, cfg.max);
|
|
170
|
-
drawGraph(visibleData, boxType + '-graph', cfg.min, cfg.max, t === 'tws', mode === 'hercules');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Genera fisicamente le curve e le aree SVG
|
|
174
|
-
function drawGraph(d, id, min, max, isTws, isHercules) {
|
|
175
|
-
const svg = document.getElementById(id);
|
|
176
|
-
if (!svg) return;
|
|
177
|
-
|
|
178
|
-
const w = 200, h = 40;
|
|
179
|
-
const range = max - min || 1;
|
|
180
|
-
const isDepth = (id === 'depth-graph');
|
|
181
|
-
|
|
182
|
-
// Utilizza sempre il tempo reale corrente per permettere lo scorrimento continuo dei dati
|
|
183
|
-
const now = Date.now();
|
|
184
|
-
|
|
185
|
-
const box = svg.closest('.data-box');
|
|
186
|
-
const isFocused = isFocusActive && box && box.classList.contains('is-focused');
|
|
187
|
-
|
|
188
|
-
const visibleMinutes = CONFIG.graphs.historyMinutes * (isNavigating ? 1 : 2);
|
|
189
|
-
const viewportMs = visibleMinutes * 60000;
|
|
190
|
-
const viewportStart = now - viewportMs;
|
|
191
|
-
|
|
192
|
-
const visibleData = d.filter(p => p.time >= viewportStart);
|
|
193
|
-
if (visibleData.length < 2) {
|
|
194
|
-
svg.innerHTML = ""; // Svuota completamente l'SVG se il sensore è spento e i dati sono scivolati fuori scala
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const colDanger = "#ff3b30", colWarning = "#ff9800", colTws = "#2c3e50", colAws = "#5c6bc0";
|
|
199
|
-
const colDepth = "#0088cc", colStw = "#00C851", colSog = "#ffbb33", colVmg = "#00b8d4";
|
|
200
|
-
|
|
201
|
-
const getColorProps = (val) => {
|
|
202
|
-
const baseStroke = isFocused ? "4.2" : "1.6";
|
|
203
|
-
const alertStroke = isFocused ? "4.8" : "2.2";
|
|
204
|
-
const warnStroke = isFocused ? "4.4" : "1.8";
|
|
205
|
-
|
|
206
|
-
let color = colTws, opacity = "0.15", stroke = baseStroke;
|
|
207
|
-
if (isTws) {
|
|
208
|
-
const baseWind = (displayModeTws === 'AWS') ? colAws : colTws;
|
|
209
|
-
const r1 = CONFIG.graphs.reef1 || 15;
|
|
210
|
-
const r2 = CONFIG.graphs.reef2 || 20;
|
|
211
|
-
const r3 = r2 + (r2 - r1); // Calcolo sintetico del 3° Terzarolo (Storm)
|
|
212
|
-
|
|
213
|
-
if (val >= r3) { color = "#9c27b0"; opacity = "0.65"; stroke = alertStroke; } // Viola (Tempesta / 3a Mano)
|
|
214
|
-
else if (val >= r2) { color = colDanger; opacity = "0.55"; stroke = alertStroke; } // Rosso (Pericolo / 2a Mano)
|
|
215
|
-
else if (val >= r1) { color = colWarning; opacity = "0.45"; stroke = warnStroke; } // Arancione (Allerta / 1a Mano)
|
|
216
|
-
else color = baseWind;
|
|
217
|
-
} else if (isDepth) {
|
|
218
|
-
if (val < CONFIG.alarms.depthDanger) { color = colDanger; opacity = "0.55"; stroke = alertStroke; }
|
|
219
|
-
else if (val < CONFIG.alarms.depthWarning) { color = colWarning; opacity = "0.45"; stroke = warnStroke; }
|
|
220
|
-
else color = colDepth;
|
|
221
|
-
} else {
|
|
222
|
-
if (id === 'stw-graph') color = colStw;
|
|
223
|
-
else if (id === 'sog-graph') color = (displayModeSog === 'VMG') ? colVmg : colSog;
|
|
224
|
-
}
|
|
225
|
-
return { color, opacity, stroke };
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
let grids = "";
|
|
229
|
-
[0.25, 0.5, 0.75].forEach(p => grids += `<line x1="0" y1="${h-(p*h)}" x2="${w}" y2="${h-(p*h)}" stroke="rgba(0,0,0,0.12)" stroke-width="0.5" vector-effect="non-scaling-stroke" />`);
|
|
230
|
-
|
|
231
|
-
const gridInterval = (visibleMinutes <= 15) ? 1 : 5;
|
|
232
|
-
for (let m = gridInterval; m < visibleMinutes; m += gridInterval) {
|
|
233
|
-
const x = w - ((m / visibleMinutes) * w);
|
|
234
|
-
grids += `<line x1="${x}" y1="0" x2="${x}" y2="${h}" stroke="rgba(0,0,0,0.08)" stroke-width="0.4" vector-effect="non-scaling-stroke" />`;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (isDepth) {
|
|
238
|
-
const dangerVal = CONFIG.alarms.depthDanger;
|
|
239
|
-
const warningVal = CONFIG.alarms.depthWarning;
|
|
240
|
-
const marginX = 4;
|
|
241
|
-
const alarmStrokeWidth = isFocused ? "4.8" : "1.8";
|
|
242
|
-
|
|
243
|
-
if (dangerVal >= min && dangerVal <= max) {
|
|
244
|
-
const p = (dangerVal - min) / range;
|
|
245
|
-
const y = h - (p * h);
|
|
246
|
-
grids += `<line x1="${marginX}" y1="${y}" x2="${w - marginX}" y2="${y}" stroke="rgba(255, 59, 48, 0.95)" stroke-width="${alarmStrokeWidth}" stroke-dasharray="12, 6, 2, 6" vector-effect="non-scaling-stroke" />`;
|
|
247
|
-
}
|
|
248
|
-
if (warningVal >= min && warningVal <= max) {
|
|
249
|
-
const p = (warningVal - min) / range;
|
|
250
|
-
const y = h - (p * h);
|
|
251
|
-
grids += `<line x1="${marginX}" y1="${y}" x2="${w - marginX}" y2="${y}" stroke="rgba(255, 204, 0, 0.95)" stroke-width="${alarmStrokeWidth}" stroke-dasharray="6 , 2, 6, 12" vector-effect="non-scaling-stroke" />`;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let gradientStops = "", lines = "", areaPath = "";
|
|
256
|
-
let started = false;
|
|
257
|
-
|
|
258
|
-
for (let i = 1; i < visibleData.length; i++) {
|
|
259
|
-
const pA = visibleData[i - 1];
|
|
260
|
-
const pB = visibleData[i];
|
|
261
|
-
|
|
262
|
-
const x1 = ((pA.time - viewportStart) / viewportMs) * w;
|
|
263
|
-
const x2 = ((pB.time - viewportStart) / viewportMs) * w;
|
|
264
|
-
const y1 = h - (Math.max(0, Math.min(1, (pA.val - min) / range)) * h);
|
|
265
|
-
const y2 = h - (Math.max(0, Math.min(1, (pB.val - min) / range)) * h);
|
|
266
|
-
|
|
267
|
-
const props = getColorProps(pB.val);
|
|
268
|
-
const deltaTime = pB.time - pA.time;
|
|
269
|
-
const expectedInterval = viewportMs / CONFIG.graphs.samples;
|
|
270
|
-
const isGap = deltaTime > (expectedInterval * 2.5);
|
|
271
|
-
|
|
272
|
-
const offset1 = (x1 / w) * 100, offset2 = (x2 / w) * 100;
|
|
273
|
-
gradientStops += `<stop offset="${offset1}%" stop-color="${props.color}" stop-opacity="${props.opacity}" />`;
|
|
274
|
-
gradientStops += `<stop offset="${offset2}%" stop-color="${props.color}" stop-opacity="${props.opacity}" />`;
|
|
275
|
-
|
|
276
|
-
// CALCOLO ELASTICO DELL'INTERRUZIONE: Tollera buchi storici fino a 90 secondi (es. i 30s di einstein)
|
|
277
|
-
// per permettere di disegnare linee solide e continue anche se mostrate in una timeline corta e veloce
|
|
278
|
-
const maxAllowedGap = Math.max(expectedInterval * 2.5, 90000);
|
|
279
|
-
const isHistoryGap = deltaTime > maxAllowedGap;
|
|
280
|
-
|
|
281
|
-
if (isHistoryGap) {
|
|
282
|
-
if (started) {
|
|
283
|
-
areaPath += `L ${x1} ${h} `;
|
|
284
|
-
started = false;
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
287
|
-
lines += `<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" style="stroke:${props.color}; stroke-width:${props.stroke}; stroke-linecap:round; shape-rendering:geometricPrecision;" vector-effect="non-scaling-stroke" />`;
|
|
288
|
-
if (!started) {
|
|
289
|
-
areaPath += `M ${Math.max(0, x1)} ${h} L ${Math.max(0, x1)} ${y1} `;
|
|
290
|
-
started = true;
|
|
291
|
-
}
|
|
292
|
-
areaPath += `L ${x2} ${y2} `;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (started) {
|
|
297
|
-
const last = visibleData[visibleData.length - 1];
|
|
298
|
-
const lastX = ((last.time - viewportStart) / viewportMs) * w;
|
|
299
|
-
areaPath += `L ${lastX} ${h} Z`;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const gradId = `grad-${id}`;
|
|
303
|
-
const defs = `<defs><linearGradient id="${gradId}" x1="0" y1="0" x2="${w}" y2="0" gradientUnits="userSpaceOnUse">${gradientStops}</linearGradient></defs>`;
|
|
304
|
-
svg.innerHTML = `${defs}${grids}<path d="${areaPath}" fill="url(#${gradId})" stroke="none" />${lines}`;
|
|
305
|
-
}
|