speedandcurrent 1.0.0
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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/correctionModel.png +0 -0
- package/correctionTable.js +179 -0
- package/currentModel.png +0 -0
- package/index.js +592 -0
- package/models.md +26 -0
- package/package.json +34 -0
- package/public/TableRenderer.js +186 -0
- package/public/app.js +278 -0
- package/public/icon.png +0 -0
- package/public/index.html +107 -0
- package/public/style.css +184 -0
- package/public/surfaceplot.html +78 -0
- package/public/vectors.css +59 -0
- package/public/vectors.html +30 -0
- package/public/vectors.js +165 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
class TableRenderer {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.formatColumnHeader = this.defaultDisplay;
|
|
4
|
+
this.formatRowHeader = this.defaultDisplay;
|
|
5
|
+
this.formatCell = this.defaultDisplay;
|
|
6
|
+
this.formatCellX = this.defaultDisplay;
|
|
7
|
+
this.formatCellY = this.defaultDisplay;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
setColumnHeaderFormat(columnHeader) {
|
|
11
|
+
columnHeader != null ? this.formatColumnHeader = columnHeader : this.formatColumnHeader = this.defaultDisplay;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setRowHeaderFormat(rowHeader) {
|
|
15
|
+
rowHeader != null ? this.formatRowHeader = rowHeader : this.formatRowHeader = this.defaultDisplay;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setCellFormat(cell) {
|
|
19
|
+
cell != null ? this.formatCell = cell : this.formatCell = this.defaultDisplay;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setCellFormatX(cellX) {
|
|
23
|
+
cellX != null ? this.formatCellX = cellX : this.formatCellX = this.defaultDisplay;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
setCellFormatY(cellY) {
|
|
27
|
+
cellY != null ? this.formatCellY = cellY : this.formatCellY = this.defaultDisplay;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render(data) {
|
|
31
|
+
const { row, col, table, selected, id, label } = data;
|
|
32
|
+
this.data = data;
|
|
33
|
+
|
|
34
|
+
// Create a table element
|
|
35
|
+
const tableElement = document.createElement('table');
|
|
36
|
+
tableElement.id = id;
|
|
37
|
+
tableElement.classList.add('Table2D');
|
|
38
|
+
// create header row
|
|
39
|
+
tableElement.appendChild(this.headerRow(col, label));
|
|
40
|
+
|
|
41
|
+
// Create other rows
|
|
42
|
+
var rIndex = 0;
|
|
43
|
+
for (let r = row.min; r <= row.max + 0.01; r += row.step) {
|
|
44
|
+
tableElement.appendChild(this.tableRow(r, rIndex, col, table, selected));
|
|
45
|
+
rIndex++;
|
|
46
|
+
}
|
|
47
|
+
return tableElement;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
headerRow(col, label) {
|
|
51
|
+
// Create label cell
|
|
52
|
+
const headerRow = document.createElement('tr');
|
|
53
|
+
const firstHeaderCell = document.createElement('th');
|
|
54
|
+
firstHeaderCell.innerText = label ? label : '';
|
|
55
|
+
firstHeaderCell.classList.add('TableRowHeader');
|
|
56
|
+
headerRow.appendChild(firstHeaderCell);
|
|
57
|
+
|
|
58
|
+
// Create header row
|
|
59
|
+
for (let c = col.min; c <= col.max + 0.01; c += col.step) {
|
|
60
|
+
const headerCell = document.createElement('th');
|
|
61
|
+
headerCell.innerHTML = this.formatColumnHeader(c);
|
|
62
|
+
headerCell.classList.add('TablecolumnHeader');
|
|
63
|
+
headerRow.appendChild(headerCell);
|
|
64
|
+
}
|
|
65
|
+
return headerRow;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
rowLabel(r) {
|
|
69
|
+
const headerCell = document.createElement('th');
|
|
70
|
+
headerCell.innerHTML = this.formatRowHeader(r);
|
|
71
|
+
//headerCell.innerText = `${cSpeed(r)} `;
|
|
72
|
+
headerCell.classList.add('TableRowHeader');
|
|
73
|
+
return headerCell;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
tableRow(r, rIndex, col, table, selected) {
|
|
77
|
+
var cIndex = 0;
|
|
78
|
+
const rowElement = document.createElement('tr');
|
|
79
|
+
rowElement.appendChild(this.rowLabel(r));
|
|
80
|
+
for (let c = col.min; c <= col.max + 0.01; c += col.step) {
|
|
81
|
+
const cell = document.createElement('td');
|
|
82
|
+
cell.classList.add('TableCell');
|
|
83
|
+
const value = table[rIndex][cIndex];
|
|
84
|
+
const text = this.formatCell(value, r, c);
|
|
85
|
+
if (text != null) {
|
|
86
|
+
cell.innerHTML = text;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
cell.classList.add('emptyCell');
|
|
90
|
+
}
|
|
91
|
+
if (selected[0] == rIndex && selected[1] == cIndex) cell.classList.add('selectedCell');
|
|
92
|
+
|
|
93
|
+
rowElement.appendChild(cell);
|
|
94
|
+
cIndex++;
|
|
95
|
+
}
|
|
96
|
+
return rowElement;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
defaultDisplay(value) {
|
|
100
|
+
return value.toFixed(2);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createSurfacePlot() {
|
|
104
|
+
const { row, col, table, id } = this.data;
|
|
105
|
+
|
|
106
|
+
// Format the axes values
|
|
107
|
+
const x = [];
|
|
108
|
+
for (let c = col.min; c <= col.max + 0.01; c += col.step) {
|
|
109
|
+
x.push(this.formatColumnHeader(c));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const y = [];
|
|
113
|
+
for (let r = row.min; r <= row.max + 0.01; r += row.step) {
|
|
114
|
+
y.push(this.formatRowHeader(r));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
const zX = table.map(row => row.map(cell => cell.N > 0 ? cell.x : null)); // Assuming 'x' values for the surface plot
|
|
119
|
+
const zY = table.map(row => row.map(cell => cell.N > 0 ? cell.y : null)); // Assuming 'y' values for the surface plot
|
|
120
|
+
|
|
121
|
+
// Open a new window
|
|
122
|
+
const newWindow = window.open('', 'Correction Plot', 'width=1024,height=800');
|
|
123
|
+
newWindow.document.title = 'Correction Plot';
|
|
124
|
+
|
|
125
|
+
// Create div elements for the plots
|
|
126
|
+
const plotDivX = newWindow.document.createElement('div');
|
|
127
|
+
plotDivX.id = 'surfacePlotX';
|
|
128
|
+
plotDivX.style.width = '100%';
|
|
129
|
+
plotDivX.style.height = '400px';
|
|
130
|
+
|
|
131
|
+
const plotDivY = newWindow.document.createElement('div');
|
|
132
|
+
plotDivY.id = 'surfacePlotY';
|
|
133
|
+
plotDivY.style.width = '100%';
|
|
134
|
+
plotDivY.style.height = '400px';
|
|
135
|
+
|
|
136
|
+
newWindow.document.body.appendChild(plotDivX);
|
|
137
|
+
newWindow.document.body.appendChild(plotDivY);
|
|
138
|
+
|
|
139
|
+
// Data for the surface plots
|
|
140
|
+
const plotDataX = [{
|
|
141
|
+
type: 'surface',
|
|
142
|
+
x: x,
|
|
143
|
+
y: y,
|
|
144
|
+
z: zX
|
|
145
|
+
}];
|
|
146
|
+
|
|
147
|
+
const plotDataY = [{
|
|
148
|
+
type: 'surface',
|
|
149
|
+
x: x,
|
|
150
|
+
y: y,
|
|
151
|
+
z: zY
|
|
152
|
+
}];
|
|
153
|
+
|
|
154
|
+
// Layout for the surface plots
|
|
155
|
+
const layoutX = {
|
|
156
|
+
title: 'X-value',
|
|
157
|
+
autosize: true,
|
|
158
|
+
scene: {
|
|
159
|
+
xaxis: { title: 'Speed' },
|
|
160
|
+
yaxis: { title: 'Heel' },
|
|
161
|
+
zaxis: { title: 'Correction' }
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const layoutY = {
|
|
166
|
+
title: 'Y-value',
|
|
167
|
+
autosize: true,
|
|
168
|
+
scene: {
|
|
169
|
+
xaxis: { title: 'Speed' },
|
|
170
|
+
yaxis: { title: 'Heel' },
|
|
171
|
+
zaxis: { title: 'Correction' }
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const config = {
|
|
176
|
+
displayModeBar: false,
|
|
177
|
+
responsive: true // Ensure the plot is responsive
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Render the plots using Plotly
|
|
181
|
+
Plotly.newPlot(plotDivX, plotDataX, layoutX, config);
|
|
182
|
+
Plotly.newPlot(plotDivY, plotDataY, layoutY, config);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default TableRenderer;
|
package/public/app.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
const API_BASE_URL = "/plugins/speedandcurrent"; // Adjust based on your server configuration
|
|
2
|
+
import TableRenderer from './tableRenderer.js';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
let updateInterval = 1000;
|
|
6
|
+
let updateTimer;
|
|
7
|
+
let updatesPaused = false;
|
|
8
|
+
|
|
9
|
+
let vAngle = 1;
|
|
10
|
+
let dAngle = 2;
|
|
11
|
+
let vSpeed = 1;
|
|
12
|
+
let dSpeed = 1;
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export function handleSpeedUnitChange(value) {
|
|
16
|
+
if (value == "knots") {
|
|
17
|
+
vSpeed = 1.943844;
|
|
18
|
+
dSpeed = 1;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (value == "kmh") {
|
|
22
|
+
vSpeed = 3.6;
|
|
23
|
+
dSpeed = 1;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
vSpeed = 1;
|
|
27
|
+
dSpeed = 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function handleAngleUnitChange(value) {
|
|
31
|
+
if (value == "degrees") {
|
|
32
|
+
vAngle = 180 / Math.PI;
|
|
33
|
+
dAngle = 0;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
vAngle = 1;
|
|
37
|
+
dAngle = 2;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
export function handleTableStyleChange(value) {
|
|
43
|
+
console.log(value);
|
|
44
|
+
if (value == "cartesian") {
|
|
45
|
+
tableRenderer.setCellFormat(cartesian);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
tableRenderer.setCellFormat(polar);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function cAngle(value) {
|
|
53
|
+
value *= vAngle;
|
|
54
|
+
return value.toFixed(dAngle);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function cSpeed(value) {
|
|
58
|
+
value *= vSpeed;
|
|
59
|
+
return value.toFixed(dSpeed);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cartesian(correction, speed, heel) {
|
|
63
|
+
if (correction.N == 0) return null;
|
|
64
|
+
return ` <div><strong>X:</strong> ${cSpeed(correction.x)}</div>
|
|
65
|
+
<div><strong>Y:</strong> ${cSpeed(correction.y)}</div>
|
|
66
|
+
<div><strong>N:</strong> ${correction.N}</div>
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function polar(correction, speed, heel) {
|
|
71
|
+
if (speed == 0 || correction.N == 0) return null;
|
|
72
|
+
const factor = (correction.x + speed) / speed;
|
|
73
|
+
return ` <div><strong>factor:</strong> ${factor.toFixed(2)}</div>
|
|
74
|
+
<div><strong>leeway:</strong> ${cAngle(Math.atan2(correction.y, speed))}</div>
|
|
75
|
+
<div><strong>N:</strong> ${correction.N}</div>
|
|
76
|
+
`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function formatCellX(value, speed, heel) {
|
|
80
|
+
if (value.N == 0) return null;
|
|
81
|
+
return cSpeed(value.x);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatCellY(value, speed, heel) {
|
|
85
|
+
if (value.N == 0) return null;
|
|
86
|
+
return Math.abs(cSpeed(value.y));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function formatCellXPolar(value, speed, heel) {
|
|
90
|
+
if (value.N == 0) return null;
|
|
91
|
+
const factor = (correction.x + speed) / speed;
|
|
92
|
+
return factor.toFixed(2);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatCellYPolar(value, speed, heel) {
|
|
96
|
+
if (value.N == 0) return null;
|
|
97
|
+
return Math.abs(cAngle(Math.atan2(value.y, speed)));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async function getFromServer(endpoint) {
|
|
103
|
+
try {
|
|
104
|
+
const response = await fetch(`${API_BASE_URL}/${endpoint}`, {
|
|
105
|
+
method: 'GET',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json'
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
if (response.status === 503) {
|
|
113
|
+
document.getElementById("message").innerHTML = "Plugin is not running";
|
|
114
|
+
} else {
|
|
115
|
+
document.getElementById("message").innerHTML = "Failed to fetch data";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
document.getElementById("message").innerHTML = "";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const data = await response.json();
|
|
123
|
+
return data;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error("Failed to fetch data from server:", error);
|
|
126
|
+
document.getElementById("message").innerHTML = error;
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
function updateOptions(data) {
|
|
134
|
+
const optionsContent = document.getElementById('options-content');
|
|
135
|
+
optionsContent.innerHTML = ''; // Clear previous content
|
|
136
|
+
|
|
137
|
+
const table = document.createElement('table');
|
|
138
|
+
const headerRow = document.createElement('tr');
|
|
139
|
+
headerRow.innerHTML = '<th>Option Name</th><th>Value</th>';
|
|
140
|
+
table.appendChild(headerRow);
|
|
141
|
+
|
|
142
|
+
Object.entries(data.options).forEach(([key, value]) => {
|
|
143
|
+
const type = typeof value;
|
|
144
|
+
if (type === 'string' || type === 'number' || type === 'boolean') {
|
|
145
|
+
|
|
146
|
+
const row = document.createElement('tr');
|
|
147
|
+
row.innerHTML = `<td>${key}</td><td>${value}</td>`;
|
|
148
|
+
table.appendChild(row);
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
optionsContent.appendChild(table);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
function updatePolar(data) {
|
|
157
|
+
const stepsList = document.getElementById('speeds-container');
|
|
158
|
+
stepsList.innerHTML = ''; // Clear previous steps
|
|
159
|
+
|
|
160
|
+
const table = document.createElement('table');
|
|
161
|
+
const headerRow = document.createElement('tr');
|
|
162
|
+
headerRow.innerHTML = '<th>Label</th><th>Speed</th><th>Angle</th>';
|
|
163
|
+
table.appendChild(headerRow);
|
|
164
|
+
|
|
165
|
+
data.polarSteps.forEach(step => {
|
|
166
|
+
const row = document.createElement('tr');
|
|
167
|
+
if (step.id != undefined)
|
|
168
|
+
row.classList.add(step.id);
|
|
169
|
+
row.innerHTML = `<td>${step.label}</td><td>${cSpeed(step.speed)}</td><td>${cAngle(step.angle)}</td>`;
|
|
170
|
+
table.appendChild(row);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
stepsList.appendChild(table);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function updateDelta(data) {
|
|
177
|
+
const stepsList = document.getElementById('delta-container');
|
|
178
|
+
stepsList.innerHTML = ''; // Clear previous steps
|
|
179
|
+
|
|
180
|
+
const table = document.createElement('table');
|
|
181
|
+
const headerRow = document.createElement('tr');
|
|
182
|
+
headerRow.innerHTML = '<th>Label</th><th>Delta</th>';
|
|
183
|
+
table.appendChild(headerRow);
|
|
184
|
+
|
|
185
|
+
data.deltas.forEach(step => {
|
|
186
|
+
const row = document.createElement('tr');
|
|
187
|
+
if (step.id != undefined)
|
|
188
|
+
row.classList.add(step.id);
|
|
189
|
+
row.innerHTML = `<td>${step.label}</td><td>${cAngle(step.value)}</td>`;
|
|
190
|
+
table.appendChild(row);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
stepsList.appendChild(table);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function updateAttitude(data) {
|
|
197
|
+
const attitudeContainer = document.getElementById('attitude-container');
|
|
198
|
+
attitudeContainer.innerHTML = '';
|
|
199
|
+
const table = document.createElement('table');
|
|
200
|
+
const headerRow = document.createElement('tr');
|
|
201
|
+
headerRow.innerHTML = '<th>Label</th><th>roll</th><th>pitch</th>';
|
|
202
|
+
table.appendChild(headerRow);
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
data.attitudeSteps.forEach(step => {
|
|
206
|
+
const row = document.createElement('tr');
|
|
207
|
+
if (step.id != undefined)
|
|
208
|
+
row.classList.add(step.id);
|
|
209
|
+
row.innerHTML = `<td>${step.label}</td><td>${cAngle(step.roll)}</td><td>${cAngle(step.pitch)}</td>`;
|
|
210
|
+
table.appendChild(row);
|
|
211
|
+
});
|
|
212
|
+
attitudeContainer.appendChild(table);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function updateTable(data) {
|
|
216
|
+
const tableContainer = document.getElementById('table-container');
|
|
217
|
+
tableContainer.innerHTML = '';
|
|
218
|
+
const tableElement = tableRenderer.render(data.tables[0]);
|
|
219
|
+
tableContainer.appendChild(tableElement);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
async function fetchAndUpdateData() {
|
|
226
|
+
const data = await getFromServer('getResults'); // Updated endpoint
|
|
227
|
+
if (data) {
|
|
228
|
+
//console.log(data);
|
|
229
|
+
updateOptions(data);
|
|
230
|
+
updatePolar(data);
|
|
231
|
+
updateAttitude(data);
|
|
232
|
+
updateDelta(data);
|
|
233
|
+
updateTable(data);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function startUpdates() {
|
|
238
|
+
if (updateTimer) clearInterval(updateTimer);
|
|
239
|
+
updateTimer = setInterval(fetchAndUpdateData, updateInterval);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function toggleUpdates() {
|
|
243
|
+
updatesPaused = !updatesPaused;
|
|
244
|
+
const toggleButton = document.getElementById('toggle-updates');
|
|
245
|
+
|
|
246
|
+
if (updatesPaused) {
|
|
247
|
+
clearInterval(updateTimer);
|
|
248
|
+
toggleButton.textContent = "Resume";
|
|
249
|
+
} else {
|
|
250
|
+
toggleButton.textContent = "Pause";
|
|
251
|
+
startUpdates();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
//document.getElementById('toggle-updates').addEventListener('click', toggleUpdates);
|
|
260
|
+
|
|
261
|
+
handleSpeedUnitChange("knots");
|
|
262
|
+
handleAngleUnitChange("degrees");
|
|
263
|
+
|
|
264
|
+
const tableRenderer = new TableRenderer();
|
|
265
|
+
handleTableStyleChange("cartesian");
|
|
266
|
+
tableRenderer.setColumnHeaderFormat(cAngle);
|
|
267
|
+
tableRenderer.setRowHeaderFormat(cSpeed);
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
// Attach functions to the window object to make them globally accessible
|
|
271
|
+
window.handleSpeedUnitChange = handleSpeedUnitChange;
|
|
272
|
+
window.handleAngleUnitChange = handleAngleUnitChange;
|
|
273
|
+
window.handleTableStyleChange = handleTableStyleChange;
|
|
274
|
+
window.toggleUpdates = toggleUpdates;
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
// Initial fetch and start updates
|
|
278
|
+
startUpdates();
|
package/public/icon.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Speed and current</title>
|
|
8
|
+
<link rel="stylesheet" href="style.css">
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
|
|
13
|
+
<header class="menu-bar">
|
|
14
|
+
<!-- Speed Unit Selection -->
|
|
15
|
+
<div class="menu-item dropdown">
|
|
16
|
+
<label for="speed-unit">Speed:</label>
|
|
17
|
+
<select id="speed-unit" onchange="handleSpeedUnitChange(this.value)">
|
|
18
|
+
<option value="ms">m/s</option>
|
|
19
|
+
<option value="knots" selected>knots</option>
|
|
20
|
+
<option value="kmh">km/h</option>
|
|
21
|
+
</select>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Angle Unit Selection -->
|
|
25
|
+
<div class="menu-item dropdown">
|
|
26
|
+
<label for="angle-unit">Angle:</label>
|
|
27
|
+
<select id="angle-unit" onchange="handleAngleUnitChange(this.value)">
|
|
28
|
+
<option value="radians">radians</option>
|
|
29
|
+
<option value="degrees" selected>degrees</option>
|
|
30
|
+
</select>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<!-- Table style Selection -->
|
|
34
|
+
<div class="menu-item dropdown">
|
|
35
|
+
<label for="table-style">Table style:</label>
|
|
36
|
+
<select id="table-style" onchange="handleTableStyleChange(this.value)">
|
|
37
|
+
<option value="cartesian" selected>cartesian</option>
|
|
38
|
+
<option value="polar" >polar</option>
|
|
39
|
+
</select>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
<!-- Pause Button -->
|
|
44
|
+
<button id="toggle-updates" class="menu-item" onclick="toggleUpdates()">Pause</button>
|
|
45
|
+
|
|
46
|
+
<!-- messages -->
|
|
47
|
+
|
|
48
|
+
<div id="message">
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
</header>
|
|
52
|
+
|
|
53
|
+
<!-- Content below the fixed menu -->
|
|
54
|
+
<main class="content">
|
|
55
|
+
|
|
56
|
+
<section class="svg-section">
|
|
57
|
+
<object data="vectors.html" width="100%" height="500px" type="text/html"></object>
|
|
58
|
+
</section>
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
<section class="delta-section">
|
|
62
|
+
<h2>Deltas</h2>
|
|
63
|
+
<div id="delta-container">
|
|
64
|
+
<!-- Speeds table will be populated here -->
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<section class="speeds-section">
|
|
69
|
+
<h2>Vectors</h2>
|
|
70
|
+
<div id="speeds-container">
|
|
71
|
+
<!-- Speeds table will be populated here -->
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<section class="attitude-section">
|
|
76
|
+
<h2>Attitude</h2>
|
|
77
|
+
<div id="attitude-container">
|
|
78
|
+
<!-- attitude table will be populated here -->
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
<section class="table-section">
|
|
84
|
+
<h2>Correction table</h2>
|
|
85
|
+
<div id="table-container">
|
|
86
|
+
<!-- Steps table will be populated here -->
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
</section>
|
|
90
|
+
|
|
91
|
+
<section class="options-section">
|
|
92
|
+
<h2>Plugin options</h2>
|
|
93
|
+
<div id="options-content">
|
|
94
|
+
<!-- Options table will be populated here -->
|
|
95
|
+
</div>
|
|
96
|
+
</section>
|
|
97
|
+
</main>
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
<script type="module" src="app.js"></script>
|
|
105
|
+
</body>
|
|
106
|
+
|
|
107
|
+
</html>
|