signalk-polar-performance-plugin 0.0.55 → 0.0.57
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 +1 -1
- 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
|
@@ -283,7 +283,7 @@ module.exports = function (app) {
|
|
|
283
283
|
addValue('performance.gybeAngleVelocityMadeGood', perfObj.runVMG)
|
|
284
284
|
if (options.targetTWA === true) {
|
|
285
285
|
addValue('performance.targetVelocityMadeGood', perfObj.runVMG, {
|
|
286
|
-
units: '
|
|
286
|
+
units: 'm/s'
|
|
287
287
|
})
|
|
288
288
|
}
|
|
289
289
|
}
|
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>
|