signalk-polar-performance-plugin 0.0.56 → 0.0.58
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/package.json +1 -1
- package/plugin/index.js +76 -33
- package/public/index.html +238 -75
- package/public/jquery-3.7.1-min.js +2 -0
- package/ratio_off.dump +0 -8047
package/package.json
CHANGED
package/plugin/index.js
CHANGED
|
@@ -243,89 +243,132 @@ module.exports = function (app) {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
if (BSP) {
|
|
246
|
-
addValue('performance.boatSpeedDamped', BSP, {
|
|
246
|
+
addValue('performance.boatSpeedDamped', BSP, {
|
|
247
|
+
units: 'm/s',
|
|
248
|
+
description: 'Boat speed after applying damping factor (see Polar Performance Plugin settings).'
|
|
249
|
+
})
|
|
247
250
|
}
|
|
248
251
|
|
|
249
252
|
if (TWA) {
|
|
250
253
|
addValue('environment.wind.angleTrueWaterDamped', TWA * port, {
|
|
251
|
-
units: 'rad'
|
|
254
|
+
units: 'rad',
|
|
255
|
+
description: 'True Wind Angle after applying damping factor, negative to port (see Polar Performance Plugin settings).'
|
|
252
256
|
})
|
|
253
257
|
}
|
|
254
258
|
|
|
255
259
|
if (typeof perfObj.beatAngle !== 'undefined') {
|
|
256
260
|
if (options.beatAngle === true) {
|
|
257
|
-
addValue('performance.beatAngle', perfObj.beatAngle * port
|
|
261
|
+
addValue('performance.beatAngle', perfObj.beatAngle * port, {
|
|
262
|
+
units: 'rad',
|
|
263
|
+
description: 'The optimal beat/upwind angle for current TWS, negative to port.'
|
|
264
|
+
})
|
|
258
265
|
}
|
|
259
266
|
if (options.targetTWA === true) {
|
|
260
|
-
addValue('performance.targetAngle', perfObj.beatAngle * port
|
|
267
|
+
addValue('performance.targetAngle', perfObj.beatAngle * port, {
|
|
268
|
+
units: 'rad',
|
|
269
|
+
description: 'The combined and automatic switching optimal beat or run angle for current TWS, negative to port.'
|
|
270
|
+
})
|
|
261
271
|
}
|
|
262
272
|
}
|
|
263
273
|
|
|
264
|
-
if (typeof perfObj.
|
|
265
|
-
|
|
274
|
+
if (typeof perfObj.runAngle !== 'undefined') {
|
|
275
|
+
if (options.beatAngle === true) {
|
|
276
|
+
addValue('performance.gybeAngle', perfObj.runAngle * port, {
|
|
277
|
+
units: 'rad',
|
|
278
|
+
description: 'The optimal run/downwind angle for current TWS, negative to port.'
|
|
279
|
+
})
|
|
280
|
+
}
|
|
266
281
|
if (options.targetTWA === true) {
|
|
267
|
-
addValue('performance.
|
|
268
|
-
units: '
|
|
282
|
+
addValue('performance.targetAngle', perfObj.runAngle * port, {
|
|
283
|
+
units: 'rad',
|
|
284
|
+
description: 'The combined and automatic switching optimal beat or run angle for current TWS, negative to port.'
|
|
269
285
|
})
|
|
270
286
|
}
|
|
271
287
|
}
|
|
272
288
|
|
|
273
|
-
if (typeof perfObj.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
if (typeof perfObj.beatVMG !== 'undefined') {
|
|
290
|
+
addValue('performance.beatAngleVelocityMadeGood', perfObj.beatVMG, {
|
|
291
|
+
units: 'm/s',
|
|
292
|
+
description: 'The beat/upwind Velocity Made Good for current boat speed and heading.'
|
|
293
|
+
})
|
|
277
294
|
if (options.targetTWA === true) {
|
|
278
|
-
addValue('performance.
|
|
295
|
+
addValue('performance.targetVelocityMadeGood', perfObj.beatVMG, {
|
|
296
|
+
units: 'm/s',
|
|
297
|
+
description: 'The combined and automatic switching beat or run Velocity Made Good for current boat speed and heading.'
|
|
298
|
+
})
|
|
279
299
|
}
|
|
280
300
|
}
|
|
281
301
|
|
|
282
302
|
if (typeof perfObj.runVMG !== 'undefined') {
|
|
283
|
-
addValue('performance.gybeAngleVelocityMadeGood', perfObj.runVMG
|
|
303
|
+
addValue('performance.gybeAngleVelocityMadeGood', perfObj.runVMG, {
|
|
304
|
+
units: 'm/s',
|
|
305
|
+
description: 'The run/downwind Velocity Made Good for current boat speed and heading.'
|
|
306
|
+
})
|
|
284
307
|
if (options.targetTWA === true) {
|
|
285
308
|
addValue('performance.targetVelocityMadeGood', perfObj.runVMG, {
|
|
286
|
-
units: '
|
|
309
|
+
units: 'm/s',
|
|
310
|
+
description: 'The combined and automatic switching beat or run Velocity Made Good for current TWS and TWA.'
|
|
287
311
|
})
|
|
288
312
|
}
|
|
289
313
|
}
|
|
290
314
|
|
|
291
315
|
if (typeof perfObj.optimumWindAngle !== 'undefined') {
|
|
292
316
|
addValue('performance.optimumWindAngle', perfObj.optimumWindAngle, {
|
|
293
|
-
units: 'rad'
|
|
317
|
+
units: 'rad',
|
|
318
|
+
description: 'The optimum wind angle, negative to port (diff between TWA and environment.wind.directionTrue).'
|
|
294
319
|
})
|
|
295
320
|
}
|
|
296
321
|
|
|
297
322
|
if (typeof perfObj.targetSpeed !== 'undefined') {
|
|
298
|
-
addValue('performance.targetSpeed', perfObj.targetSpeed
|
|
323
|
+
addValue('performance.targetSpeed', perfObj.targetSpeed, {
|
|
324
|
+
units: 'm/s',
|
|
325
|
+
description: 'Target boat speed based on current TWA.'
|
|
326
|
+
}
|
|
327
|
+
)
|
|
299
328
|
}
|
|
300
329
|
|
|
301
330
|
if (typeof perfObj.polarSpeed !== 'undefined') {
|
|
302
|
-
addValue('performance.polarSpeed', perfObj.polarSpeed
|
|
303
|
-
|
|
331
|
+
addValue('performance.polarSpeed', perfObj.polarSpeed, {
|
|
332
|
+
units: 'm/s',
|
|
333
|
+
description: 'The polar chart boat speed as per the polar chart for current TWS and TWA.'
|
|
334
|
+
})
|
|
335
|
+
addValue('performance.polarSpeedRatio', perfObj.polarSpeedRatio, {
|
|
336
|
+
units: 'ratio',
|
|
337
|
+
description: 'The ratio between actual boat speed and polar chart boat speed for current TWS and TWA.'
|
|
338
|
+
})
|
|
304
339
|
if (typeof perfObj.velocityMadeGood !== 'undefined') {
|
|
305
|
-
addValue('performance.velocityMadeGood', perfObj.velocityMadeGood
|
|
306
|
-
|
|
307
|
-
'
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
340
|
+
addValue('performance.velocityMadeGood', perfObj.velocityMadeGood, {
|
|
341
|
+
units: 'm/s',
|
|
342
|
+
description: 'The actual Velocity Made Good based on current heading and boat speed.'
|
|
343
|
+
})
|
|
344
|
+
addValue('performance.polarVelocityMadeGood', perfObj.polarVelocityMadeGood, {
|
|
345
|
+
units: 'm/s',
|
|
346
|
+
description: 'The polar chart Velocity Made Good indicated in the polar for current TWS and TWA.'
|
|
347
|
+
})
|
|
348
|
+
addValue('performance.polarVelocityMadeGoodRatio', perfObj.polarVelocityMadeGoodRatio, {
|
|
349
|
+
units: 'ratio',
|
|
350
|
+
description: 'The ratio between actual Velocity Made Good and polar chart indicated Velocity Made Good for current TWS and TWA.'
|
|
351
|
+
})
|
|
316
352
|
}
|
|
317
353
|
}
|
|
318
354
|
|
|
319
355
|
if (typeof perfObj.maxSpeed !== 'undefined') {
|
|
320
|
-
addValue('performance.maxSpeed', perfObj.maxSpeed, {
|
|
356
|
+
addValue('performance.maxSpeed', perfObj.maxSpeed, {
|
|
357
|
+
units: 'm/s',
|
|
358
|
+
description: 'Maximum boat speed as per polar chart.'
|
|
359
|
+
})
|
|
321
360
|
addValue('performance.maxSpeedAngle', perfObj.maxSpeedAngle, {
|
|
322
|
-
units: 'rad'
|
|
361
|
+
units: 'rad',
|
|
362
|
+
description: 'The angle to achieve maximum boat speed (not the VMG), based on current TWS, negative to port.'
|
|
323
363
|
})
|
|
324
364
|
}
|
|
325
365
|
|
|
326
366
|
if (options.tackTrue === true) {
|
|
327
367
|
if (typeof perfObj.tackTrue !== 'undefined') {
|
|
328
|
-
addValue('performance.tackTrue', perfObj.tackTrue
|
|
368
|
+
addValue('performance.tackTrue', perfObj.tackTrue, {
|
|
369
|
+
units: 'rad',
|
|
370
|
+
description: 'The Opposite Tack\'s heading relative to True North, based on current TWS.'
|
|
371
|
+
})
|
|
329
372
|
}
|
|
330
373
|
}
|
|
331
374
|
|
package/public/index.html
CHANGED
|
@@ -1,51 +1,179 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
|
-
<
|
|
4
|
-
<script type='text/javascript' src=
|
|
5
|
-
<
|
|
6
|
-
<
|
|
3
|
+
<head>
|
|
4
|
+
<script type='text/javascript' src="./Chart.min.js"></script>
|
|
5
|
+
<script type='text/javascript' src="./jquery-3.7.1-min.js"></script>
|
|
6
|
+
<title>Polar performance</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { background-color: #020202; margin: 0; padding: 0; color: white; font-family: Arial, sans-serif; }
|
|
9
|
+
|
|
10
|
+
.chart-container {
|
|
11
|
+
width: 100%;
|
|
12
|
+
max-width: 1200px;
|
|
13
|
+
margin: 0 auto; /* Center the whole thing */
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* New Toolbar Styling - sits below the chart */
|
|
17
|
+
.config-toolbar {
|
|
18
|
+
display: flex;
|
|
19
|
+
justify-content: flex-end; /* Aligns content to the right */
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding: 10px 20px;
|
|
22
|
+
background-color: #0a0a0a; /* Slightly lighter than background */
|
|
23
|
+
border-top: 1px solid #222;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.config-toolbar label { font-size: 14px; color: #ccc; margin-right: 8px; }
|
|
27
|
+
|
|
28
|
+
.config-toolbar input {
|
|
29
|
+
width: 50px;
|
|
30
|
+
background: rgba(0, 0, 0, 0.5);
|
|
31
|
+
border: 1px solid #555;
|
|
32
|
+
color: white;
|
|
33
|
+
text-align: center;
|
|
34
|
+
border-radius: 2px;
|
|
35
|
+
padding: 4px;
|
|
36
|
+
font-size: 14px;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
39
|
+
</head>
|
|
40
|
+
<body>
|
|
7
41
|
|
|
8
|
-
<
|
|
42
|
+
<div class="chart-container">
|
|
43
|
+
<canvas id="myChart"></canvas>
|
|
44
|
+
|
|
45
|
+
<!-- Configuration Toolbar (Now below the chart) -->
|
|
46
|
+
<div class="config-toolbar">
|
|
47
|
+
<label for="labelPos">Place polar wind speed label at TWA:</label>
|
|
48
|
+
<input type="number" id="labelPos" value="135" min="0" max="180" onchange="updateLabelPos(this.value)">
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
9
51
|
|
|
10
52
|
<script>
|
|
11
53
|
|
|
12
|
-
var polarSpeed, boatSpeed, TWA
|
|
54
|
+
var polarSpeed, boatSpeed, TWA, TWS
|
|
13
55
|
var myChart, chartData
|
|
14
56
|
|
|
57
|
+
// GLOBAL CONFIG: Load from storage or default to 135
|
|
58
|
+
window.labelTWA = localStorage.getItem('polarLabelTWA') || 135;
|
|
59
|
+
document.getElementById('labelPos').value = window.labelTWA;
|
|
60
|
+
|
|
61
|
+
function updateLabelPos(val) {
|
|
62
|
+
window.labelTWA = val;
|
|
63
|
+
localStorage.setItem('polarLabelTWA', val);
|
|
64
|
+
if (myChart) myChart.update();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- CUSTOM PLUGIN: DRAW LABELS ---
|
|
68
|
+
Chart.plugins.register({
|
|
69
|
+
afterDraw: function(chartInstance) {
|
|
70
|
+
try {
|
|
71
|
+
var ctx = chartInstance.chart.ctx;
|
|
72
|
+
var targetAngle = parseFloat(window.labelTWA) || 135;
|
|
73
|
+
|
|
74
|
+
// 1. DRAW LABELS
|
|
75
|
+
chartInstance.data.datasets.forEach(function(dataset, i) {
|
|
76
|
+
if (i < 2) return;
|
|
77
|
+
|
|
78
|
+
var meta = chartInstance.getDatasetMeta(i);
|
|
79
|
+
if (meta.hidden) return;
|
|
80
|
+
|
|
81
|
+
var p1 = null, p2 = null;
|
|
82
|
+
var p1Meta = null, p2Meta = null;
|
|
83
|
+
|
|
84
|
+
// Find interpolation bracket
|
|
85
|
+
for (var j = 0; j < dataset.data.length - 1; j++) {
|
|
86
|
+
if (dataset.data[j].x <= targetAngle && dataset.data[j+1].x >= targetAngle) {
|
|
87
|
+
p1 = dataset.data[j];
|
|
88
|
+
p2 = dataset.data[j+1];
|
|
89
|
+
p1Meta = meta.data[j]._model;
|
|
90
|
+
p2Meta = meta.data[j+1]._model;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
var x, y;
|
|
96
|
+
if (p1 && p2) {
|
|
97
|
+
var ratio = (p2.x - p1.x === 0) ? 0 : (targetAngle - p1.x) / (p2.x - p1.x);
|
|
98
|
+
x = p1Meta.x + (p2Meta.x - p1Meta.x) * ratio;
|
|
99
|
+
y = p1Meta.y + (p2Meta.y - p1Meta.y) * ratio;
|
|
100
|
+
} else {
|
|
101
|
+
// Fallback
|
|
102
|
+
var closest = null, minDiff = 100;
|
|
103
|
+
dataset.data.forEach(function(d, idx) {
|
|
104
|
+
var diff = Math.abs(d.x - targetAngle);
|
|
105
|
+
if (diff < minDiff) { minDiff = diff; closest = meta.data[idx]._model; }
|
|
106
|
+
});
|
|
107
|
+
if (closest) { x = closest.x; y = closest.y; }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Draw only if on screen and reasonably close to request
|
|
111
|
+
if (x > 0 && y > 0) {
|
|
112
|
+
ctx.save();
|
|
113
|
+
ctx.font = "bold 14px Arial";
|
|
114
|
+
ctx.textAlign = "center";
|
|
115
|
+
ctx.textBaseline = "middle";
|
|
116
|
+
var labelText = String(dataset.label);
|
|
117
|
+
|
|
118
|
+
// Box
|
|
119
|
+
var textWidth = ctx.measureText(labelText).width;
|
|
120
|
+
ctx.fillStyle = "rgba(2,2,2,0.9)";
|
|
121
|
+
ctx.fillRect(x - (textWidth/2) - 3, y - 9, textWidth + 6, 18);
|
|
122
|
+
|
|
123
|
+
// Text
|
|
124
|
+
ctx.fillStyle = dataset.borderColor || '#ffffff';
|
|
125
|
+
ctx.fillText(labelText, x, y);
|
|
126
|
+
ctx.restore();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// 2. REDRAW DOTS (Datasets 0 and 1)
|
|
131
|
+
[0, 1].forEach(function(i) {
|
|
132
|
+
var meta = chartInstance.getDatasetMeta(i);
|
|
133
|
+
if (!meta.hidden) {
|
|
134
|
+
meta.data.forEach(function(point) {
|
|
135
|
+
point.draw();
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.log("Label draw error: " + err);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
15
146
|
$.getJSON("/plugins/signalk-polar-performance-plugin/chartData", function(json) {
|
|
16
147
|
|
|
17
|
-
// Add actual values dots
|
|
148
|
+
// 1. Add actual values dots to the BEGINNING (Indices 0 and 1)
|
|
18
149
|
json.datasets.unshift({
|
|
19
150
|
label: 'Polar Speed',
|
|
20
151
|
backgroundColor: 'rgba(0,255,0,0.7)',
|
|
21
152
|
borderColor: 'rgba(0,255,0,0.2)',
|
|
22
153
|
borderWidth: 1,
|
|
23
|
-
radius:
|
|
24
|
-
|
|
25
|
-
data: [
|
|
26
|
-
{
|
|
27
|
-
y: 0,
|
|
28
|
-
x: 0,
|
|
29
|
-
r: 10
|
|
30
|
-
}
|
|
31
|
-
]
|
|
154
|
+
radius: 10,
|
|
155
|
+
pointStyle: 'circle',
|
|
156
|
+
data: [{ y: 0, x: 0, r: 10 }]
|
|
32
157
|
},
|
|
33
158
|
{
|
|
34
159
|
label: 'Boat Speed',
|
|
35
160
|
backgroundColor: 'rgba(0,150,255,0.7)',
|
|
36
161
|
borderColor: 'rgba(0,150,255,0.2)',
|
|
37
162
|
borderWidth: 1,
|
|
38
|
-
radius:
|
|
39
|
-
|
|
40
|
-
data: [
|
|
41
|
-
{
|
|
42
|
-
y: 0,
|
|
43
|
-
x: 0,
|
|
44
|
-
r: 10
|
|
45
|
-
}
|
|
46
|
-
]
|
|
163
|
+
radius: 10,
|
|
164
|
+
pointStyle: 'circle',
|
|
165
|
+
data: [{ y: 0, x: 0, r: 10 }]
|
|
47
166
|
})
|
|
48
167
|
|
|
168
|
+
// 2. BACKUP ORIGINAL COLORS
|
|
169
|
+
for (var i = 2; i < json.datasets.length; i++) {
|
|
170
|
+
if (!json.datasets[i].borderColor) {
|
|
171
|
+
json.datasets[i].borderColor = 'rgba(100, 100, 100, 0.5)';
|
|
172
|
+
}
|
|
173
|
+
json.datasets[i]._originalColor = json.datasets[i].borderColor;
|
|
174
|
+
json.datasets[i].borderWidth = 1;
|
|
175
|
+
}
|
|
176
|
+
|
|
49
177
|
chartData = json
|
|
50
178
|
showChart(chartData)
|
|
51
179
|
})
|
|
@@ -65,25 +193,8 @@ function showChart (data) {
|
|
|
65
193
|
},
|
|
66
194
|
spanGaps: true,
|
|
67
195
|
scales: {
|
|
68
|
-
x: {
|
|
69
|
-
type: 'linear',
|
|
70
|
-
ticks: {
|
|
71
|
-
color: 'white',
|
|
72
|
-
font: {
|
|
73
|
-
size: 14
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
y: {
|
|
78
|
-
type: 'linear',
|
|
79
|
-
ticks: {
|
|
80
|
-
color: 'white',
|
|
81
|
-
font: {
|
|
82
|
-
size: 14
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
196
|
xAxes: [{
|
|
197
|
+
type: 'linear',
|
|
87
198
|
scaleLabel: {
|
|
88
199
|
display: true,
|
|
89
200
|
labelString: 'True wind angle (TWA)',
|
|
@@ -101,6 +212,7 @@ function showChart (data) {
|
|
|
101
212
|
}
|
|
102
213
|
}],
|
|
103
214
|
yAxes: [{
|
|
215
|
+
type: 'linear',
|
|
104
216
|
scaleLabel: {
|
|
105
217
|
display: true,
|
|
106
218
|
labelString: 'Target boat speed (POL SPD)',
|
|
@@ -122,8 +234,6 @@ function showChart (data) {
|
|
|
122
234
|
enabled: true,
|
|
123
235
|
callbacks: {
|
|
124
236
|
label: function (tooltipItems, data) {
|
|
125
|
-
console.log("tooltipItems: " + JSON.stringify(tooltipItems))
|
|
126
|
-
console.log("data: " + data)
|
|
127
237
|
let label = tooltipItems.xLabel + "° " + tooltipItems.yLabel + " kts"
|
|
128
238
|
return label
|
|
129
239
|
}
|
|
@@ -148,74 +258,61 @@ function connect () {
|
|
|
148
258
|
console.log("connect()")
|
|
149
259
|
ws = new WebSocket((window.location.protocol === 'https:' ? 'wss' : 'ws') + "://" + window.location.host + "/signalk/v1/stream?subscribe=none");
|
|
150
260
|
ws.onopen = function() {
|
|
151
|
-
|
|
152
|
-
// Start listening for value updates
|
|
153
261
|
startListeners();
|
|
154
|
-
|
|
155
262
|
ws.onmessage = function(event) {
|
|
156
263
|
if (event.data.includes('signalk-server')) {
|
|
157
|
-
|
|
158
|
-
console.log("Skipping welcome message: " + welcomeMessage)
|
|
264
|
+
console.log("Skipping welcome message")
|
|
159
265
|
} else {
|
|
160
266
|
handleData(JSON.parse(event.data));
|
|
161
267
|
}
|
|
162
268
|
}
|
|
163
|
-
|
|
164
|
-
ws.
|
|
165
|
-
console.log("WebSocket closed")
|
|
166
|
-
setTimeout(connect, 500)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
ws.onerror = function(err) {
|
|
170
|
-
console.log("WebSocket connection error: " + err.message + " - closing connection");
|
|
171
|
-
setTimeout(connect, 500)
|
|
172
|
-
}
|
|
173
|
-
|
|
269
|
+
ws.onclose = function() { setTimeout(connect, 500) }
|
|
270
|
+
ws.onerror = function(err) { setTimeout(connect, 500) }
|
|
174
271
|
}
|
|
175
272
|
}
|
|
176
273
|
|
|
177
274
|
window.addEventListener('focus', function () {
|
|
178
275
|
if (ws.readyState == 0) {
|
|
179
|
-
console.log("Restarting websocket")
|
|
180
276
|
connect()
|
|
181
277
|
}
|
|
182
278
|
})
|
|
183
279
|
|
|
184
280
|
function startListeners () {
|
|
185
|
-
var paths = [
|
|
281
|
+
var paths = [
|
|
282
|
+
{'path': 'environment.wind.angleTrueWaterDamped'},
|
|
283
|
+
{'path': 'performance.polarSpeed'},
|
|
284
|
+
{'path': 'performance.boatSpeedDamped'},
|
|
285
|
+
{'path': 'environment.wind.speedTrue'}
|
|
286
|
+
]
|
|
186
287
|
|
|
187
288
|
var subscriptionObject = {
|
|
188
289
|
"context": "vessels.self",
|
|
189
290
|
"policy" : "ideal",
|
|
190
|
-
"minPeriod":
|
|
291
|
+
"minPeriod": 1000,
|
|
191
292
|
"subscribe": paths
|
|
192
293
|
}
|
|
193
294
|
|
|
194
|
-
|
|
195
|
-
console.log("subscriptionMessage: " + subscriptionMessage);
|
|
196
|
-
ws.send(subscriptionMessage);
|
|
295
|
+
ws.send(JSON.stringify(subscriptionObject));
|
|
197
296
|
}
|
|
198
297
|
|
|
199
298
|
function handleData (data) {
|
|
200
|
-
if (typeof data.updates[0].meta != 'undefined')
|
|
201
|
-
|
|
202
|
-
}
|
|
299
|
+
if (typeof data.updates[0].meta != 'undefined') return
|
|
300
|
+
|
|
203
301
|
var path = data.updates[0].values[0].path
|
|
204
302
|
var value = data.updates[0].values[0].value
|
|
205
303
|
|
|
206
304
|
if (path == 'performance.polarSpeed') {
|
|
207
305
|
polarSpeed = roundDec(msToKts(value), 1)
|
|
208
|
-
// console.log('polarSpeed set to %s', polarSpeed)
|
|
209
306
|
chartData.datasets[0].data[0].y = polarSpeed
|
|
210
307
|
} else if (path == 'environment.wind.angleTrueWaterDamped') {
|
|
211
308
|
TWA = roundDec(Math.abs(radToDeg(value)),1)
|
|
212
|
-
// console.log('TWA set to %s', TWA)
|
|
213
309
|
chartData.datasets[0].data[0].x = TWA
|
|
214
310
|
chartData.datasets[1].data[0].x = TWA
|
|
215
311
|
} else if (path == 'performance.boatSpeedDamped') {
|
|
216
312
|
boatSpeed = roundDec(msToKts(value),1)
|
|
217
|
-
// console.log('boatSpeed set to %s', boatSpeed)
|
|
218
313
|
chartData.datasets[1].data[0].y = boatSpeed
|
|
314
|
+
} else if (path == 'environment.wind.speedTrue') {
|
|
315
|
+
TWS = msToKts(value);
|
|
219
316
|
}
|
|
220
317
|
}
|
|
221
318
|
|
|
@@ -225,7 +322,72 @@ connect()
|
|
|
225
322
|
setInterval(updateChart, 300)
|
|
226
323
|
|
|
227
324
|
function updateChart () {
|
|
228
|
-
myChart
|
|
325
|
+
if (myChart) {
|
|
326
|
+
interpolateColors(TWS);
|
|
327
|
+
myChart.update();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// --- Interpolate Colors ---
|
|
332
|
+
function interpolateColors(tws) {
|
|
333
|
+
if (!tws || !chartData) return;
|
|
334
|
+
|
|
335
|
+
for (var i = 2; i < chartData.datasets.length; i++) {
|
|
336
|
+
var dataset = chartData.datasets[i];
|
|
337
|
+
var lineSpeed = parseFloat(dataset.label);
|
|
338
|
+
|
|
339
|
+
if (isNaN(lineSpeed)) continue;
|
|
340
|
+
|
|
341
|
+
var originalColor = dataset._originalColor;
|
|
342
|
+
|
|
343
|
+
if (!originalColor) {
|
|
344
|
+
originalColor = "rgba(100,100,100,0.5)";
|
|
345
|
+
dataset._originalColor = originalColor;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
var diff = Math.abs(tws - lineSpeed);
|
|
349
|
+
var influenceRange = 2.5;
|
|
350
|
+
var weight = Math.max(0, 1 - (diff / influenceRange));
|
|
351
|
+
|
|
352
|
+
if (weight < 0.05) {
|
|
353
|
+
dataset.borderColor = originalColor;
|
|
354
|
+
dataset.borderWidth = 1;
|
|
355
|
+
dataset.pointBorderColor = originalColor;
|
|
356
|
+
dataset.pointBackgroundColor = originalColor;
|
|
357
|
+
} else {
|
|
358
|
+
var blended = blendColors(originalColor, "#FFFFFF", weight);
|
|
359
|
+
dataset.borderColor = blended;
|
|
360
|
+
dataset.borderWidth = 1 + (3 * weight);
|
|
361
|
+
dataset.pointBorderColor = blended;
|
|
362
|
+
dataset.pointBackgroundColor = blended;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function blendColors(color1, color2, weight) {
|
|
368
|
+
var c1 = parseColor(color1);
|
|
369
|
+
var c2 = parseColor(color2);
|
|
370
|
+
var r = Math.round(c1[0] + (c2[0] - c1[0]) * weight);
|
|
371
|
+
var g = Math.round(c1[1] + (c2[1] - c1[1]) * weight);
|
|
372
|
+
var b = Math.round(c1[2] + (c2[2] - c1[2]) * weight);
|
|
373
|
+
return "rgb(" + r + "," + g + "," + b + ")";
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function parseColor(input) {
|
|
377
|
+
if (!input) return [100,100,100];
|
|
378
|
+
if (input.substr(0,1) == "#") {
|
|
379
|
+
var col = input.slice(1);
|
|
380
|
+
if (col.length == 3) col = col[0]+col[0]+col[1]+col[1]+col[2]+col[2];
|
|
381
|
+
var int = parseInt(col, 16);
|
|
382
|
+
return [(int >> 16) & 255, (int >> 8) & 255, int & 255];
|
|
383
|
+
}
|
|
384
|
+
else if (input.substr(0,3) == "rgb") {
|
|
385
|
+
var parts = input.match(/(\d+)/g);
|
|
386
|
+
if (parts && parts.length >= 3) {
|
|
387
|
+
return [parseInt(parts[0]), parseInt(parts[1]), parseInt(parts[2])];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return [100,100,100];
|
|
229
391
|
}
|
|
230
392
|
|
|
231
393
|
function radToDeg(radians) {
|
|
@@ -246,4 +408,5 @@ function roundDec (value, decimals) {
|
|
|
246
408
|
}
|
|
247
409
|
|
|
248
410
|
</script>
|
|
249
|
-
|
|
411
|
+
</body>
|
|
412
|
+
</html>
|