@sailingrotevista/rotevista-dash 6.1.4 → 6.2.3
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.
- package/app.js +53 -13
- package/index.html +0 -1
- package/index.js +396 -56
- package/package.json +1 -1
- package/radar copia.html +490 -0
- package/radar.html +747 -0
- package/style.css +5 -5
package/radar copia.html
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="it">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Wind Radar - Twin Light Compass Calibrata (v6.0)</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
/* Dimensione dinamica reattiva del widget quadrato */
|
|
10
|
+
--box-size: 400px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
background-color: #ffffff;
|
|
15
|
+
color: #000000;
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: 20px;
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
h1 {
|
|
28
|
+
color: #000;
|
|
29
|
+
font-size: 1.3rem;
|
|
30
|
+
margin-bottom: 5px;
|
|
31
|
+
letter-spacing: 1px;
|
|
32
|
+
text-transform: uppercase;
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
p {
|
|
37
|
+
color: #666;
|
|
38
|
+
font-size: 0.85rem;
|
|
39
|
+
margin-top: 0;
|
|
40
|
+
margin-bottom: 25px;
|
|
41
|
+
text-align: center;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.container {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
gap: 30px;
|
|
48
|
+
align-items: flex-start;
|
|
49
|
+
justify-content: center;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.controls-panel {
|
|
53
|
+
background: #fdfdfd;
|
|
54
|
+
border: 1px solid #e5e5e5;
|
|
55
|
+
padding: 20px;
|
|
56
|
+
border-radius: 12px;
|
|
57
|
+
width: 320px;
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.control-group {
|
|
63
|
+
margin-bottom: 15px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.control-group label {
|
|
67
|
+
display: block;
|
|
68
|
+
font-size: 0.75rem;
|
|
69
|
+
font-weight: bold;
|
|
70
|
+
color: #666;
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
margin-bottom: 8px;
|
|
73
|
+
letter-spacing: 0.5px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.control-group input[type="range"] {
|
|
77
|
+
width: 100%;
|
|
78
|
+
accent-color: #00C851;
|
|
79
|
+
background: #ddd;
|
|
80
|
+
height: 4px;
|
|
81
|
+
border-radius: 2px;
|
|
82
|
+
outline: none;
|
|
83
|
+
cursor: pointer;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.val-display {
|
|
87
|
+
float: right;
|
|
88
|
+
color: #000;
|
|
89
|
+
font-weight: bold;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* BOX STRUTTURALE SCALABILE */
|
|
93
|
+
.data-box-square {
|
|
94
|
+
position: relative;
|
|
95
|
+
width: var(--box-size);
|
|
96
|
+
height: var(--box-size);
|
|
97
|
+
background: rgb(255, 255, 255);
|
|
98
|
+
border: 1px solid #eee;
|
|
99
|
+
border-radius: 12px;
|
|
100
|
+
box-sizing: border-box;
|
|
101
|
+
display: flex;
|
|
102
|
+
align-items: center;
|
|
103
|
+
justify-content: center;
|
|
104
|
+
overflow: hidden;
|
|
105
|
+
transition: width 0.1s ease, height 0.1s ease;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.label-row {
|
|
109
|
+
position: absolute;
|
|
110
|
+
top: 10px;
|
|
111
|
+
left: 12px;
|
|
112
|
+
right: 12px;
|
|
113
|
+
display: flex;
|
|
114
|
+
justify-content: space-between;
|
|
115
|
+
align-items: baseline;
|
|
116
|
+
pointer-events: none;
|
|
117
|
+
z-index: 10;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.label {
|
|
121
|
+
color: #888;
|
|
122
|
+
font-size: 0.65rem;
|
|
123
|
+
font-weight: bold;
|
|
124
|
+
text-transform: uppercase;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.unit {
|
|
128
|
+
color: #aaa;
|
|
129
|
+
font-size: 0.6rem;
|
|
130
|
+
font-weight: bold;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Forza l'SVG a riempire lo spazio a disposizione mantenendo le proporzioni */
|
|
134
|
+
svg {
|
|
135
|
+
display: block;
|
|
136
|
+
width: 100%;
|
|
137
|
+
height: 100%;
|
|
138
|
+
max-height: 100%;
|
|
139
|
+
object-fit: contain;
|
|
140
|
+
}
|
|
141
|
+
</style>
|
|
142
|
+
</head>
|
|
143
|
+
<body>
|
|
144
|
+
|
|
145
|
+
<h1>Tactical Wind Radar (v6.0)</h1>
|
|
146
|
+
<p>Calibrazione Spaziatura: Compressione proporzionale degli anelli con luce di 5px dal dial</p>
|
|
147
|
+
|
|
148
|
+
<div class="container">
|
|
149
|
+
|
|
150
|
+
<div class="controls-panel">
|
|
151
|
+
|
|
152
|
+
<!-- SLIDER DI ZOOM SCALABILITÀ -->
|
|
153
|
+
<div class="control-group" style="padding-bottom: 15px; border-bottom: 1px solid #eee; margin-bottom: 20px;">
|
|
154
|
+
<label style="color: #0088cc;">Zoom Box / Scalabilità <span class="val-display" id="scale-val" style="color: #0088cc;">400 px</span></label>
|
|
155
|
+
<input type="range" id="slider-scale" min="200" max="800" value="400" step="10" oninput="resizeBox()">
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="control-group">
|
|
159
|
+
<label>Reef 1 (Limite 1) <span class="val-display" id="r1-val">18 kts</span></label>
|
|
160
|
+
<input type="range" id="slider-r1" min="10" max="22" value="18" step="1" oninput="updateReefs()">
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div class="control-group">
|
|
164
|
+
<label>Reef 2 (Limite 2) <span class="val-display" id="r2-val">24 kts</span></label>
|
|
165
|
+
<input type="range" id="slider-r2" min="16" max="32" value="24" step="1" oninput="updateReefs()">
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<div class="control-group" style="border-top: 1px solid #eee; padding-top: 15px; margin-bottom: 0;">
|
|
169
|
+
<label>Reef 3 (Storm) <span class="val-display" id="r3-val" style="color: #9c27b0;">30 kts</span></label>
|
|
170
|
+
<span style="font-size: 0.7rem; color: #888; display: block; margin-top: -3px; line-height: 1.2;">
|
|
171
|
+
Calcolato dinamicamente: R2 + (R2 - R1)
|
|
172
|
+
</span>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div class="data-box-square">
|
|
177
|
+
<div class="label-row">
|
|
178
|
+
<span class="label">TWD (HISTORICAL)</span>
|
|
179
|
+
<span class="unit">ECMWF + N2K</span>
|
|
180
|
+
</div>
|
|
181
|
+
|
|
182
|
+
<!-- BUSSOLA RADAR SVG (Esatto ViewBox dell'originale, nativamente scalabile) -->
|
|
183
|
+
<svg id="wind-radar" viewBox="35 38 330 395" preserveAspectRatio="xMidYMid meet">
|
|
184
|
+
<defs id="radar-gradients">
|
|
185
|
+
<clipPath id="boat-clip"><circle cx="200" cy="200" r="50" /></clipPath>
|
|
186
|
+
<linearGradient id="axiom-grad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
187
|
+
<stop offset="0%" style="stop-color:#333333;stop-opacity:1" />
|
|
188
|
+
<stop offset="100%" style="stop-color:#999999;stop-opacity:1" />
|
|
189
|
+
</linearGradient>
|
|
190
|
+
|
|
191
|
+
<filter id="center-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
192
|
+
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#aaaaaa" flood-opacity="0.5" />
|
|
193
|
+
</filter>
|
|
194
|
+
</defs>
|
|
195
|
+
|
|
196
|
+
<!-- I tre sfondi speculari originali diurni con coordinate RGB esatte -->
|
|
197
|
+
<circle cx="200" cy="200" r="160" fill="rgb(252, 252, 252)" />
|
|
198
|
+
<circle cx="200" cy="200" r="125" fill="rgb(240, 240, 240)" />
|
|
199
|
+
|
|
200
|
+
<!-- Ticks originali generati dinamicamente -->
|
|
201
|
+
<g id="ticks"></g>
|
|
202
|
+
|
|
203
|
+
<!-- Etichette cardinali N/S/E/W -->
|
|
204
|
+
<g id="tick-labels" fill="#000000" text-anchor="middle" dominant-baseline="middle" font-family="Arial" font-weight="bold">
|
|
205
|
+
<text font-size="16" transform="translate(200, 74)">N</text>
|
|
206
|
+
<text font-size="16" transform="translate(326, 200) rotate(90)">E</text>
|
|
207
|
+
<text font-size="16" transform="translate(74, 200) rotate(-90)">W</text>
|
|
208
|
+
<text font-size="16" transform="translate(200, 326) rotate(180)">S</text>
|
|
209
|
+
|
|
210
|
+
<text font-size="11" transform="translate(262.5, 91.7) rotate(30)">30</text>
|
|
211
|
+
<text font-size="11" transform="translate(308.3, 137.5) rotate(60)">60</text>
|
|
212
|
+
<text font-size="11" transform="translate(308.3, 262.5) rotate(120)">120</text>
|
|
213
|
+
<text font-size="11" transform="translate(262.5, 308.3) rotate(150)">150</text>
|
|
214
|
+
<text font-size="11" transform="translate(137.5, 91.7) rotate(-30)">30</text>
|
|
215
|
+
<text font-size="11" transform="translate(91.7, 137.5) rotate(-60)">60</text>
|
|
216
|
+
<text font-size="11" transform="translate(91.7, 262.5) rotate(-120)">120</text>
|
|
217
|
+
<text font-size="11" transform="translate(137.5, 308.3) rotate(-150)">150</text>
|
|
218
|
+
</g>
|
|
219
|
+
|
|
220
|
+
<!-- 1. ORBITA DEL PRESENTE "ORA" SUL LIVELLO DI SFONDO (Sotto gli archi) -->
|
|
221
|
+
<!-- Posizionata esattamente sulla mezzeria del raggio dell'Anello 1 (67.2px) -->
|
|
222
|
+
<circle id="ora-orbit" cx="200" cy="200" r="67.2" fill="none" stroke="#cfd8dc" stroke-width="1.2" stroke-dasharray="4, 4" />
|
|
223
|
+
|
|
224
|
+
<!-- Gli anelli radar degli archi verranno stampati qui -->
|
|
225
|
+
<g id="radar-rings"></g>
|
|
226
|
+
|
|
227
|
+
<!-- Cerchio centrale interattivo originale e Barca NERA SOLIDA -->
|
|
228
|
+
<circle id="fullscreen-hotspot" cx="200" cy="200" r="55" fill="rgb(238, 238, 238)" stroke="#e0e0e0" stroke-width="1" filter="url(#center-glow)" cursor="pointer" />
|
|
229
|
+
<path id="boat-icon" d="M200,150 Q165,185 170,250 Q165,190 200,173 Q235,190 230,250 Q235,185 200,150 Z"
|
|
230
|
+
fill="#000000" transform="translate(0, 5)" clip-path="url(#boat-clip)" style="pointer-events: none;" />
|
|
231
|
+
</svg>
|
|
232
|
+
</div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<script>
|
|
236
|
+
// Funzione per testare dinamicamente il ridimensionamento del div
|
|
237
|
+
function resizeBox() {
|
|
238
|
+
const size = document.getElementById('slider-scale').value;
|
|
239
|
+
// Modifichiamo la variabile CSS, l'SVG seguirà nativamente
|
|
240
|
+
document.documentElement.style.setProperty('--box-size', size + 'px');
|
|
241
|
+
document.getElementById('scale-val').innerText = size + " px";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const CALM_THRESHOLD_KTS = 1.5;
|
|
245
|
+
|
|
246
|
+
// Default Reef settings
|
|
247
|
+
let REEF1 = 18;
|
|
248
|
+
let REEF2 = 24;
|
|
249
|
+
let REEF3 = 30;
|
|
250
|
+
|
|
251
|
+
// Dati di simulazione calibrati su 8 anelli reali con la correzione ripristinata dei diametri
|
|
252
|
+
const mockData = [
|
|
253
|
+
{ isFuture: true, twdMin: 235, twdMax: 245, twsPeak: 5.0 }, // Anello 0 (r:59.0px): Previsione futura (Bianco Solido - 100% OPACITÀ CON TRATTEGGIO)
|
|
254
|
+
{ isActive: true, twdMin: 220, twdMax: 260, twsPeak: 11.5 }, // Anello 1 (r:67.2px): Presente Mobile (Sfumatura Bianco -> Verde -> Bianco)
|
|
255
|
+
{ twdMin: 225, twdMax: 255, twsPeak: 13.5 }, // Anello 2 (r:75.4px): Ora -0.5 (Verde Solido)
|
|
256
|
+
{ twdMin: 230, twdMax: 250, twsPeak: 16.0 }, // Anello 3 (r:83.6px): Ora -1.0 (Sfumatura Verde -> Arancio -> Verde)
|
|
257
|
+
{ twdMin: 235, twdMax: 245, twsPeak: 20.0 }, // Anello 4 (r:91.8px): Ora -1.5 (Arancio Solido)
|
|
258
|
+
{ twdMin: 220, twdMax: 260, twsPeak: 22.5 }, // Anello 5 (r:100.0px): Ora -2.0 (Sfumatura Arancio -> Rosso -> Arancio)
|
|
259
|
+
{ twdMin: 230, twdMax: 250, twsPeak: 25.5 }, // Anello 6 (r:108.2px): Ora -2.5 (Rosso Solido)
|
|
260
|
+
{ twdMin: 215, twdMax: 265, twsPeak: 28.5 } // Anello 7 (r:116.4px): Ora -3.0 (Sfumatura Rosso -> Viola -> Rosso)
|
|
261
|
+
];
|
|
262
|
+
|
|
263
|
+
// --- MAPPA RADIALE SPECULARE COMPRESSA AL MILLIMETRO SU 8 ANELLI ---
|
|
264
|
+
// r_i = 59.0px + 8.2px * i (Incastonati in sicurezza, massimo raggio a 116.4px)
|
|
265
|
+
const ringRadii = [59.0, 67.2, 75.4, 83.6, 91.8, 100.0, 108.2, 116.4];
|
|
266
|
+
const chronologicalMapping = [0, 1, 2, 3, 4, 5, 6, 7];
|
|
267
|
+
|
|
268
|
+
const ARC_STROKE_WIDTH = 5.0; // Spessore arco ridotto a 5.0px per una visualizzazione affilatissima
|
|
269
|
+
const BORDER_STROKE_WIDTH = ARC_STROKE_WIDTH + 2; // Spessore 7.0px (bordo da 1px per lato)
|
|
270
|
+
|
|
271
|
+
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
|
|
272
|
+
const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
|
|
273
|
+
return {
|
|
274
|
+
x: centerX + (radius * Math.cos(angleInRadians)),
|
|
275
|
+
y: centerY + (radius * Math.sin(angleInRadians))
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function describeArc(centerX, centerY, radius, startAngle, endAngle) {
|
|
280
|
+
const start = polarToCartesian(centerX, centerY, radius, endAngle);
|
|
281
|
+
const end = polarToCartesian(centerX, centerY, radius, startAngle);
|
|
282
|
+
|
|
283
|
+
let arcSweep = endAngle - startAngle;
|
|
284
|
+
if (arcSweep < 0) arcSweep += 360;
|
|
285
|
+
|
|
286
|
+
const largeArcFlag = arcSweep <= 180 ? "0" : "1";
|
|
287
|
+
|
|
288
|
+
return [
|
|
289
|
+
"M", start.x, start.y,
|
|
290
|
+
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
|
|
291
|
+
].join(" ");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function drawCompassTicks() {
|
|
295
|
+
const ticksGroup = document.getElementById('ticks');
|
|
296
|
+
ticksGroup.innerHTML = '';
|
|
297
|
+
for (let i = 0; i < 360; i += 10) {
|
|
298
|
+
const line = document.createElementNS("http://www.w3.org/2000/svg", "line");
|
|
299
|
+
const isMajor = (i % 30 === 0);
|
|
300
|
+
|
|
301
|
+
line.setAttribute("x1", "200");
|
|
302
|
+
line.setAttribute("y1", "40");
|
|
303
|
+
line.setAttribute("x2", "200");
|
|
304
|
+
line.setAttribute("y2", isMajor ? "60" : "50");
|
|
305
|
+
|
|
306
|
+
line.setAttribute("stroke", isMajor ? "#000000" : "#bbbbbb");
|
|
307
|
+
line.setAttribute("stroke-width", isMajor ? "2" : "1");
|
|
308
|
+
line.setAttribute("transform", `rotate(${i}, 200, 200)`);
|
|
309
|
+
ticksGroup.appendChild(line);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function updateReefs() {
|
|
314
|
+
REEF1 = parseInt(document.getElementById('slider-r1').value);
|
|
315
|
+
REEF2 = parseInt(document.getElementById('slider-r2').value);
|
|
316
|
+
REEF3 = REEF2 + (REEF2 - REEF1);
|
|
317
|
+
|
|
318
|
+
document.getElementById('r1-val').innerText = REEF1 + " kts";
|
|
319
|
+
document.getElementById('r2-val').innerText = REEF2 + " kts";
|
|
320
|
+
document.getElementById('r3-val').innerText = REEF3 + " kts";
|
|
321
|
+
|
|
322
|
+
renderRadar();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// --- LA NUOVA SCALA A COLORE DI PROSSIMITÀ CON CONTRASTO RICALIBRATO ---
|
|
326
|
+
function getChordAlignedGradient(id, radius, tws, startAngle, endAngle) {
|
|
327
|
+
const startPt = polarToCartesian(200, 200, radius, endAngle);
|
|
328
|
+
const endPt = polarToCartesian(200, 200, radius, startAngle);
|
|
329
|
+
|
|
330
|
+
let stops = '';
|
|
331
|
+
const R1 = REEF1;
|
|
332
|
+
const R2 = REEF2;
|
|
333
|
+
const R3 = REEF3;
|
|
334
|
+
const colorEdge = 'rgb(240, 240, 240)';
|
|
335
|
+
|
|
336
|
+
// 1. BIANCO SOLIDO (0 - 40% di R1)
|
|
337
|
+
if (tws < R1 * 0.4) {
|
|
338
|
+
return { type: 'solid', color: '#ffffff' };
|
|
339
|
+
}
|
|
340
|
+
// 2. TRANSIZIONE BIANCO -> VERDE (40% - 60% di R1)
|
|
341
|
+
else if (tws >= R1 * 0.4 && tws < R1 * 0.6) {
|
|
342
|
+
stops = `
|
|
343
|
+
<stop offset="0%" stop-color="#ffffff" />
|
|
344
|
+
<stop offset="50%" stop-color="#00C851" />
|
|
345
|
+
<stop offset="100%" stop-color="#ffffff" />
|
|
346
|
+
`;
|
|
347
|
+
}
|
|
348
|
+
// 3. VERDE SOLIDO (60% - 75% di R1) -> Brezza stabile
|
|
349
|
+
else if (tws >= R1 * 0.6 && tws < R1 * 0.75) {
|
|
350
|
+
return { type: 'solid', color: '#00C851' };
|
|
351
|
+
}
|
|
352
|
+
// 4. TRANSIZIONE VERDE -> ARANCIONE (75% R1 - R1) -> Raffiche in aumento
|
|
353
|
+
else if (tws >= R1 * 0.75 && tws < R1) {
|
|
354
|
+
stops = `
|
|
355
|
+
<stop offset="0%" stop-color="#00C851" />
|
|
356
|
+
<stop offset="50%" stop-color="#ff9800" />
|
|
357
|
+
<stop offset="100%" stop-color="#00C851" />
|
|
358
|
+
`;
|
|
359
|
+
}
|
|
360
|
+
// 5. ARANCIONE SOLIDO (R1 -> Metà di R2) -> Vento forte stabile
|
|
361
|
+
else if (tws >= R1 && tws < R1 + (R2 - R1) * 0.5) {
|
|
362
|
+
return { type: 'solid', color: '#ff9800' };
|
|
363
|
+
}
|
|
364
|
+
// 6. TRANSIZIONE ARANCIONE -> ROSSO (Metà R2 -> R2) -> RICALIBRATO AD ALTO CONTRASTO (Dorato -> Cremisi)
|
|
365
|
+
else if (tws >= R1 + (R2 - R1) * 0.5 && tws < R2) {
|
|
366
|
+
stops = `
|
|
367
|
+
<stop offset="0%" stop-color="#ffaa00" /> <!-- Arancio Dorato Brillante sui bordi -->
|
|
368
|
+
<stop offset="50%" stop-color="#d50000" /> <!-- Rosso Cremisi Intenso e saturo al centro -->
|
|
369
|
+
<stop offset="100%" stop-color="#ffaa00" />
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
// 7. ROSSO SOLIDO (R2 -> Metà di R3) -> Burrasca stabile
|
|
373
|
+
else if (tws >= R2 && tws < R2 + (R3 - R2) * 0.5) {
|
|
374
|
+
return { type: 'solid', color: '#ff3b30' };
|
|
375
|
+
}
|
|
376
|
+
// 8. TRANSIZIONE ROSSO -> VIOLA (Metà R3 -> R3) -> Transizione Spettrale (Rosso vivo -> Viola)
|
|
377
|
+
else if (tws >= R2 + (R3 - R2) * 0.5 && tws < R3) {
|
|
378
|
+
stops = `
|
|
379
|
+
<stop offset="0%" stop-color="#ff3b30" />
|
|
380
|
+
<stop offset="50%" stop-color="#9c27b0" />
|
|
381
|
+
<stop offset="100%" stop-color="#ff3b30" />
|
|
382
|
+
`;
|
|
383
|
+
}
|
|
384
|
+
// 9. VIOLA SOLIDO (Oltre R3) -> Tempesta stabile
|
|
385
|
+
else {
|
|
386
|
+
return { type: 'solid', color: '#9c27b0' };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const xml = `
|
|
390
|
+
<linearGradient id="${id}" x1="${startPt.x.toFixed(1)}" y1="${startPt.y.toFixed(1)}" x2="${endPt.x.toFixed(1)}" y2="${endPt.y.toFixed(1)}" gradientUnits="userSpaceOnUse">
|
|
391
|
+
${stops}
|
|
392
|
+
</linearGradient>
|
|
393
|
+
`;
|
|
394
|
+
|
|
395
|
+
return { type: 'gradient', xml: xml, url: `url(#${id})` };
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function renderRadar() {
|
|
399
|
+
const ringsContainer = document.getElementById('radar-rings');
|
|
400
|
+
const defsContainer = document.getElementById('radar-gradients');
|
|
401
|
+
|
|
402
|
+
defsContainer.innerHTML = `
|
|
403
|
+
<clipPath id="boat-clip"><circle cx="200" cy="200" r="50" /></clipPath>
|
|
404
|
+
<linearGradient id="axiom-grad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
405
|
+
<stop offset="0%" style="stop-color:#333333;stop-opacity:1" />
|
|
406
|
+
<stop offset="100%" style="stop-color:#999999;stop-opacity:1" />
|
|
407
|
+
</linearGradient>
|
|
408
|
+
<filter id="center-glow" x="-20%" y="-20%" width="140%" height="140%">
|
|
409
|
+
<feDropShadow dx="0" dy="0" stdDeviation="8" flood-color="#aaaaaa" flood-opacity="0.5" />
|
|
410
|
+
</filter>
|
|
411
|
+
`;
|
|
412
|
+
ringsContainer.innerHTML = '';
|
|
413
|
+
|
|
414
|
+
// Imposta dinamicamente i raggi in base a X per renderli scalabili
|
|
415
|
+
const oraOrbit = document.getElementById('ora-orbit');
|
|
416
|
+
if (oraOrbit) oraOrbit.setAttribute("r", 67.2);
|
|
417
|
+
|
|
418
|
+
mockData.forEach((data, index) => {
|
|
419
|
+
const radius = ringRadii[index];
|
|
420
|
+
const gradId = `chord-gradient-${index}`;
|
|
421
|
+
|
|
422
|
+
// IMPORTANTE: Rimosso il dimezzamento dell'opacità per la previsione.
|
|
423
|
+
// Ora l'opacità è sempre 1 (100% solida) per tutti gli anelli,
|
|
424
|
+
// garantendo che il Bianco sia puro e brillante, senza sbiadire in grigio!
|
|
425
|
+
const opacityValue = 1;
|
|
426
|
+
|
|
427
|
+
// --- 1. CASO CALMA PIATTA (Cerchio grigio-azzurro discreto sottile) ---
|
|
428
|
+
if (data.twsPeak < CALM_THRESHOLD_KTS) {
|
|
429
|
+
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
|
|
430
|
+
circle.setAttribute("cx", "200");
|
|
431
|
+
circle.setAttribute("cy", "200");
|
|
432
|
+
circle.setAttribute("r", radius);
|
|
433
|
+
circle.setAttribute("fill", "none");
|
|
434
|
+
circle.setAttribute("stroke", "#b0bec5");
|
|
435
|
+
circle.setAttribute("stroke-width", "1.2");
|
|
436
|
+
circle.setAttribute("stroke-dasharray", "4, 4");
|
|
437
|
+
ringsContainer.appendChild(circle);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// --- 2. CASO ARCO DIREZIONALE ---
|
|
442
|
+
const grad = getChordAlignedGradient(gradId, radius, data.twsPeak, data.twdMin, data.twdMax);
|
|
443
|
+
let strokeColor = '';
|
|
444
|
+
|
|
445
|
+
if (grad.type === 'gradient') {
|
|
446
|
+
defsContainer.innerHTML += grad.xml;
|
|
447
|
+
strokeColor = grad.url;
|
|
448
|
+
} else {
|
|
449
|
+
strokeColor = grad.color;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const pathData = describeArc(200, 200, radius, data.twdMin, data.twdMax);
|
|
453
|
+
|
|
454
|
+
const borderPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
455
|
+
borderPath.setAttribute("d", pathData);
|
|
456
|
+
borderPath.setAttribute("fill", "none");
|
|
457
|
+
borderPath.setAttribute("stroke", "#000000");
|
|
458
|
+
borderPath.setAttribute("stroke-width", BORDER_STROKE_WIDTH);
|
|
459
|
+
borderPath.setAttribute("stroke-linecap", "round");
|
|
460
|
+
borderPath.setAttribute("opacity", opacityValue);
|
|
461
|
+
|
|
462
|
+
// Tratteggio per l'Anello Previsione (Anello 0)
|
|
463
|
+
if (data.isFuture) {
|
|
464
|
+
borderPath.setAttribute("stroke-dasharray", "10, 6");
|
|
465
|
+
}
|
|
466
|
+
ringsContainer.appendChild(borderPath);
|
|
467
|
+
|
|
468
|
+
const mainPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
469
|
+
mainPath.setAttribute("d", pathData);
|
|
470
|
+
mainPath.setAttribute("fill", "none");
|
|
471
|
+
mainPath.setAttribute("stroke", strokeColor);
|
|
472
|
+
mainPath.setAttribute("stroke-width", ARC_STROKE_WIDTH);
|
|
473
|
+
mainPath.setAttribute("stroke-linecap", "round");
|
|
474
|
+
mainPath.setAttribute("opacity", opacityValue);
|
|
475
|
+
|
|
476
|
+
// Tratteggio per l'Anello Previsione (Anello 0)
|
|
477
|
+
if (data.isFuture) {
|
|
478
|
+
mainPath.setAttribute("stroke-dasharray", "10, 6");
|
|
479
|
+
}
|
|
480
|
+
ringsContainer.appendChild(mainPath);
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
window.onload = function() {
|
|
485
|
+
drawCompassTicks();
|
|
486
|
+
renderRadar();
|
|
487
|
+
};
|
|
488
|
+
</script>
|
|
489
|
+
</body>
|
|
490
|
+
</html>
|