speedandcurrent 1.4.2 → 1.5.1

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/public/app.js CHANGED
@@ -12,40 +12,55 @@ let vSpeed = 1;
12
12
  let dSpeed = 1;
13
13
 
14
14
 
15
+ const LS_KEYS = {
16
+ speed: 'sc_speed_unit',
17
+ angle: 'sc_angle_unit',
18
+ color: 'sc_color_mode',
19
+ metrics: 'sc_table_metrics'
20
+ };
21
+
15
22
  export function handleSpeedUnitChange(value) {
16
23
  if (value == "knots") {
17
24
  vSpeed = 1.943844;
18
25
  dSpeed = 1;
26
+ localStorage.setItem(LS_KEYS.speed, 'knots');
19
27
  return;
20
28
  }
21
29
  if (value == "kmh") {
22
30
  vSpeed = 3.6;
23
31
  dSpeed = 1;
32
+ localStorage.setItem(LS_KEYS.speed, 'kmh');
24
33
  return;
25
34
  }
26
35
  vSpeed = 1;
27
36
  dSpeed = 1;
37
+ localStorage.setItem(LS_KEYS.speed, 'ms');
28
38
  }
29
39
 
30
40
  export function handleAngleUnitChange(value) {
31
41
  if (value == "degrees") {
32
42
  vAngle = 180 / Math.PI;
33
43
  dAngle = 0;
44
+ localStorage.setItem(LS_KEYS.angle, 'degrees');
34
45
  return;
35
46
  }
36
47
  vAngle = 1;
37
48
  dAngle = 2;
49
+ localStorage.setItem(LS_KEYS.angle, 'radians');
38
50
  }
39
51
 
40
52
 
41
53
 
42
- export function handleTableStyleChange(value) {
43
- //console.log(value);
44
- if (value == "cartesian") {
45
- tableRenderer.setCellFormat(cartesian);
46
- }
47
- else {
48
- tableRenderer.setCellFormat(polar);
54
+ // Removed table style switch; rendering is driven by metrics selection.
55
+
56
+ export function handleColorModeChange(value) {
57
+ if (tableRenderer && tableRenderer.setColorMode) {
58
+ tableRenderer.setColorMode(value);
59
+ localStorage.setItem(LS_KEYS.color, value);
60
+ // re-render immediately using last data if available
61
+ // fetchAndUpdateData will repaint next tick; do a quick repaint now if cached
62
+ // (Simplest approach: trigger fetch)
63
+ fetchAndUpdateData();
49
64
  }
50
65
  }
51
66
 
@@ -59,42 +74,83 @@ function cSpeed(value) {
59
74
  return value.toFixed(dSpeed);
60
75
  }
61
76
 
77
+ let selectedMetrics = {
78
+ correction: true,
79
+ factor: true,
80
+ leeway: true,
81
+ trace: false,
82
+ N: true,
83
+ };
84
+
85
+ function buildCellContent(parts) {
86
+ // parts is array of strings; filter falsy and join
87
+ return parts.filter(Boolean).join('\n');
88
+ }
89
+
62
90
  function cartesian(correction, speed, heel) {
63
91
  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
- `;
92
+ const bits = [];
93
+ if (selectedMetrics.correction) {
94
+ bits.push(` <div><strong>X:</strong> ${cSpeed(correction.x)}</div>`);
95
+ bits.push(` <div><strong>Y:</strong> ${cSpeed(correction.y)}</div>`);
96
+ }
97
+ if (selectedMetrics.trace && Number.isFinite(correction.trace)) {
98
+ bits.push(` <div><strong>trace:</strong> ${Number(correction.trace).toPrecision(2)}</div>`);
99
+ }
100
+ if (selectedMetrics.N) {
101
+ bits.push(` <div><strong>N:</strong> ${correction.N}</div>`);
102
+ }
103
+ return buildCellContent(bits);
68
104
  }
69
105
 
70
106
  function polar(correction, speed, heel) {
71
107
  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);
108
+ const bits = [];
109
+ // Prefer server-provided values when available
110
+ const factor = Number.isFinite(correction.factor) ? correction.factor : (speed > 0 ? (correction.x + speed) / speed : 0);
111
+ const leewayRad = Number.isFinite(correction.leeway) ? correction.leeway : Math.atan2(correction.y, speed + correction.x);
112
+ const leewayDisplay = leewayRad; // signed value; color uses |leeway|
113
+
114
+ if (selectedMetrics.factor) {
115
+ bits.push(` <div><strong>factor:</strong> ${factor.toFixed(2)}</div>`);
116
+ }
117
+ if (selectedMetrics.leeway) {
118
+ bits.push(` <div><strong>leeway:</strong> ${cAngle(leewayDisplay)}</div>`);
119
+ }
120
+ if (selectedMetrics.trace && Number.isFinite(correction.trace)) {
121
+ bits.push(` <div><strong>trace:</strong> ${Number(correction.trace).toPrecision(2)}</div>`);
122
+ }
123
+ if (selectedMetrics.N) {
124
+ bits.push(` <div><strong>N:</strong> ${correction.N}</div>`);
125
+ }
126
+ return buildCellContent(bits);
93
127
  }
94
128
 
95
- function formatCellYPolar(value, speed, heel) {
96
- if (value.N == 0) return null;
97
- return Math.abs(cAngle(Math.atan2(value.y, speed)));
129
+ function unifiedCellRenderer(correction, speed, heel) {
130
+ // Combines polar-oriented metrics and cartesian correction based on selections
131
+ if (correction.N == 0) return null;
132
+ const bits = [];
133
+ // Polar-ish metrics
134
+ if (selectedMetrics.factor && (speed !== 0)) {
135
+ const factor = Number.isFinite(correction.factor) ? correction.factor : (speed > 0 ? (correction.x + speed) / speed : 0);
136
+ bits.push(` <div><strong>factor:</strong> ${factor.toFixed(2)}</div>`);
137
+ }
138
+ if (selectedMetrics.leeway && (speed !== 0)) {
139
+ const leewayRad = Number.isFinite(correction.leeway) ? correction.leeway : Math.atan2(correction.y, speed + correction.x);
140
+ bits.push(` <div><strong>leeway:</strong> ${cAngle(leewayRad)}</div>`);
141
+ }
142
+ // Cartesian correction (always available)
143
+ if (selectedMetrics.correction) {
144
+ bits.push(` <div><strong>X:</strong> ${cSpeed(correction.x)}</div>`);
145
+ bits.push(` <div><strong>Y:</strong> ${cSpeed(correction.y)}</div>`);
146
+ }
147
+ if (selectedMetrics.trace && Number.isFinite(correction.trace)) {
148
+ bits.push(` <div><strong>trace:</strong> ${Number(correction.trace).toPrecision(2)}</div>`);
149
+ }
150
+ if (selectedMetrics.N) {
151
+ bits.push(` <div><strong>N:</strong> ${correction.N}</div>`);
152
+ }
153
+ return buildCellContent(bits);
98
154
  }
99
155
 
100
156
 
@@ -104,26 +160,54 @@ async function getFromServer(endpoint) {
104
160
  const response = await fetch(`${API_BASE_URL}/${endpoint}`, {
105
161
  method: 'GET',
106
162
  headers: {
107
- 'Content-Type': 'application/json'
108
- }
163
+ 'Accept': 'application/json'
164
+ },
165
+ credentials: 'same-origin' // include cookies for auth
109
166
  });
110
167
 
168
+ const msgEl = document.getElementById("message");
169
+ const contentType = response.headers.get('content-type') || '';
170
+
171
+ // Auth errors: show friendly message and pause updates
172
+ if (response.status === 401 || response.status === 403) {
173
+ if (msgEl) {
174
+ msgEl.innerHTML = 'Not signed in. Please <a href="/">sign in</a> to the Signal K server, then reload this page.';
175
+ }
176
+ if (updateTimer) {
177
+ clearInterval(updateTimer);
178
+ updatesPaused = true;
179
+ const toggleButton = document.getElementById('toggle-updates');
180
+ if (toggleButton) toggleButton.textContent = "Resume";
181
+ }
182
+ return null;
183
+ }
184
+
185
+ // Service not running or other HTTP errors
111
186
  if (!response.ok) {
112
187
  if (response.status === 503) {
113
- document.getElementById("message").innerHTML = "Plugin is not running";
188
+ if (msgEl) msgEl.textContent = "Plugin is not running";
114
189
  } else {
115
- document.getElementById("message").innerHTML = "Failed to fetch data. Error: " + response.status + " " + response.statusText;
190
+ if (msgEl) msgEl.textContent = `Failed to fetch data (${response.status} ${response.statusText}).`;
116
191
  }
117
- }
118
- else {
119
- document.getElementById("message").innerHTML = "";
192
+ return null;
120
193
  }
121
194
 
122
- const data = await response.json();
123
- return data;
195
+ // Only parse JSON when content-type matches
196
+ if (contentType.includes('application/json')) {
197
+ const data = await response.json();
198
+ if (msgEl) msgEl.textContent = "";
199
+ return data;
200
+ } else {
201
+ // Likely an HTML login page or plain text error
202
+ if (msgEl) {
203
+ msgEl.innerHTML = 'Unexpected server response. If you are not signed in, please <a href="/">sign in</a> to the Signal K server, then reload.';
204
+ }
205
+ return null;
206
+ }
124
207
  } catch (error) {
125
208
  console.error("Failed to fetch data from server:", error);
126
- document.getElementById("message").innerHTML = error;
209
+ const msgEl = document.getElementById("message");
210
+ if (msgEl) msgEl.textContent = `Network error: ${error && error.message ? error.message : String(error)}`;
127
211
  return null;
128
212
  }
129
213
  }
@@ -160,7 +244,7 @@ function updatePolar(data) {
160
244
  const table = document.createElement('table');
161
245
  table.classList.add('polar');
162
246
  const headerRow = document.createElement('tr');
163
- headerRow.innerHTML = '<th>Label</th><th>Speed</th><th>Angle</th>';
247
+ headerRow.innerHTML = '<th>Label</th><th>Speed</th><th>Angle</th><th>Trace</th>';
164
248
  table.appendChild(headerRow);
165
249
 
166
250
  data.polars.forEach(polar => {
@@ -168,7 +252,10 @@ function updatePolar(data) {
168
252
  if (polar.id) {
169
253
  row.id = polar.id;
170
254
  }
171
- row.innerHTML = `<td>${polar.displayAttributes.label}</td><td>${cSpeed(polar.magnitude)}</td><td>${cAngle(polar.angle)}</td>`;
255
+ if (polar.displayAttributes.unstable) {
256
+ row.classList.add('unstable');
257
+ }
258
+ row.innerHTML = `<td>${polar.displayAttributes.label}</td><td>${cSpeed(polar.magnitude)}</td><td>${cAngle(polar.angle)}</td><td>${(polar.trace !== undefined && polar.trace !== null ? Number(polar.trace).toPrecision(2) : '')}</td>`;
172
259
  table.appendChild(row);
173
260
  });
174
261
 
@@ -182,7 +269,7 @@ function updateDelta(data) {
182
269
  const table = document.createElement('table');
183
270
  table.classList.add('delta');
184
271
  const headerRow = document.createElement('tr');
185
- headerRow.innerHTML = '<th>Label</th><th>Value</th>';
272
+ headerRow.innerHTML = '<th>Label</th><th>Value</th><th>Variance</th>';
186
273
  table.appendChild(headerRow);
187
274
 
188
275
  data.deltas.forEach(delta => {
@@ -190,7 +277,10 @@ function updateDelta(data) {
190
277
  if (delta.id) {
191
278
  row.id = delta.id;
192
279
  }
193
- row.innerHTML = `<td>${delta.displayAttributes.label}</td><td>${cAngle(delta.value)}</td>`;
280
+ if (delta.displayAttributes.unstable) {
281
+ row.classList.add('unstable');
282
+ }
283
+ row.innerHTML = `<td>${delta.displayAttributes.label}</td><td>${cAngle(delta.value)}</td><td>${(delta.variance !== undefined && delta.variance !== null ? Number(delta.variance).toPrecision(2) : '')}</td>`;
194
284
  table.appendChild(row);
195
285
  });
196
286
 
@@ -213,6 +303,9 @@ function updateAttitude(data) {
213
303
  if (attitude.id) {
214
304
  row.id = attitude.id;
215
305
  }
306
+ if (attitude.displayAttributes.unstable) {
307
+ row.classList.add('unstable');
308
+ }
216
309
  row.innerHTML = `<td>${attitude.displayAttributes.label}</td><td>${cAngle(attitude.value.roll)}</td><td>${cAngle(attitude.value.pitch)}</td><td>${cAngle(attitude.value.yaw)}</td>`;
217
310
  table.appendChild(row);
218
311
  });
@@ -223,7 +316,6 @@ function updateTable(data) {
223
316
  const tableContainer = document.getElementById('table-container');
224
317
  tableContainer.innerHTML = '';
225
318
  data.tables.forEach(table => {
226
-
227
319
  const tableElement = tableRenderer.render(table);
228
320
  tableContainer.appendChild(tableElement);
229
321
  });
@@ -268,21 +360,88 @@ export function toggleUpdates() {
268
360
 
269
361
  //document.getElementById('toggle-updates').addEventListener('click', toggleUpdates);
270
362
 
271
- handleSpeedUnitChange("knots");
272
- handleAngleUnitChange("degrees");
273
-
274
363
  const tableRenderer = new TableRenderer();
275
- handleTableStyleChange("cartesian");
276
364
  tableRenderer.setColumnHeaderFormat(cAngle);
277
365
  tableRenderer.setRowHeaderFormat(cSpeed);
366
+ // Default cell format is unified and driven by metrics; set to polar-like by default
367
+ tableRenderer.setCellFormat(unifiedCellRenderer);
368
+
369
+ function initSettingsFromStorage() {
370
+ try {
371
+ const speedUnit = localStorage.getItem(LS_KEYS.speed) || 'knots';
372
+ const angleUnit = localStorage.getItem(LS_KEYS.angle) || 'degrees';
373
+ const colorMode = localStorage.getItem(LS_KEYS.color) || 'none';
374
+ const metricsRaw = localStorage.getItem(LS_KEYS.metrics);
375
+
376
+ const speedEl = document.getElementById('speed-unit');
377
+ const angleEl = document.getElementById('angle-unit');
378
+ const colorEl = document.getElementById('color-mode');
379
+ const metricsEls = {
380
+ correction: document.getElementById('show-correction'),
381
+ factor: document.getElementById('show-factor'),
382
+ leeway: document.getElementById('show-leeway'),
383
+ trace: document.getElementById('show-trace'),
384
+ N: document.getElementById('show-N'),
385
+ };
386
+ if (speedEl) speedEl.value = speedUnit;
387
+ if (angleEl) angleEl.value = angleUnit;
388
+ if (colorEl) colorEl.value = colorMode;
389
+ if (metricsRaw) {
390
+ try {
391
+ const parsed = JSON.parse(metricsRaw);
392
+ selectedMetrics = { ...selectedMetrics, ...parsed };
393
+ } catch {}
394
+ }
395
+ // Initialize checkboxes to match selectedMetrics
396
+ if (metricsEls.correction) metricsEls.correction.checked = !!selectedMetrics.correction;
397
+ if (metricsEls.factor) metricsEls.factor.checked = !!selectedMetrics.factor;
398
+ if (metricsEls.leeway) metricsEls.leeway.checked = !!selectedMetrics.leeway;
399
+ if (metricsEls.trace) metricsEls.trace.checked = !!selectedMetrics.trace;
400
+ if (metricsEls.N) metricsEls.N.checked = !!selectedMetrics.N;
401
+
402
+ // Apply handlers (order: units, then renderer-dependent)
403
+ handleSpeedUnitChange(speedUnit);
404
+ handleAngleUnitChange(angleUnit);
405
+ handleColorModeChange(colorMode);
406
+ } catch (e) {
407
+ // Fallback to defaults on any error
408
+ handleSpeedUnitChange('knots');
409
+ handleAngleUnitChange('degrees');
410
+ handleColorModeChange('none');
411
+ }
412
+ }
413
+
414
+ export function handleMetricsChange() {
415
+ const metricsEls = {
416
+ correction: document.getElementById('show-correction'),
417
+ factor: document.getElementById('show-factor'),
418
+ leeway: document.getElementById('show-leeway'),
419
+ trace: document.getElementById('show-trace'),
420
+ N: document.getElementById('show-N'),
421
+ };
422
+ selectedMetrics = {
423
+ correction: metricsEls.correction ? !!metricsEls.correction.checked : selectedMetrics.correction,
424
+ factor: metricsEls.factor ? !!metricsEls.factor.checked : selectedMetrics.factor,
425
+ leeway: metricsEls.leeway ? !!metricsEls.leeway.checked : selectedMetrics.leeway,
426
+ trace: metricsEls.trace ? !!metricsEls.trace.checked : selectedMetrics.trace,
427
+ N: metricsEls.N ? !!metricsEls.N.checked : selectedMetrics.N,
428
+ };
429
+ try {
430
+ localStorage.setItem(LS_KEYS.metrics, JSON.stringify(selectedMetrics));
431
+ } catch {}
432
+ // Re-render with current data (trigger fetch or reuse last render by forcing a refresh)
433
+ fetchAndUpdateData();
434
+ }
278
435
 
279
436
 
280
437
  // Attach functions to the window object to make them globally accessible
281
438
  window.handleSpeedUnitChange = handleSpeedUnitChange;
282
439
  window.handleAngleUnitChange = handleAngleUnitChange;
283
- window.handleTableStyleChange = handleTableStyleChange;
440
+ window.handleColorModeChange = handleColorModeChange;
441
+ window.handleMetricsChange = handleMetricsChange;
284
442
  window.toggleUpdates = toggleUpdates;
285
443
 
286
444
 
287
445
  // Initial fetch and start updates
446
+ initSettingsFromStorage();
288
447
  startUpdates();
package/public/index.html CHANGED
@@ -30,16 +30,31 @@
30
30
  </select>
31
31
  </div>
32
32
 
33
- <!-- Table style Selection -->
33
+ <!-- Metrics Selection -->
34
+ <div class="menu-item" id="metrics-group">
35
+ <span>Show:</span>
36
+ <label><input type="checkbox" id="show-correction" onchange="handleMetricsChange()"> correction</label>
37
+ <label><input type="checkbox" id="show-factor" onchange="handleMetricsChange()"> factor</label>
38
+ <label><input type="checkbox" id="show-leeway" onchange="handleMetricsChange()"> leeway</label>
39
+ <label><input type="checkbox" id="show-trace" onchange="handleMetricsChange()"> trace</label>
40
+ <label><input type="checkbox" id="show-N" onchange="handleMetricsChange()"> N</label>
41
+ </div>
42
+
43
+ <!-- Color Mode Selection -->
34
44
  <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>
45
+ <label for="color-mode">Table coloring:</label>
46
+ <select id="color-mode" onchange="handleColorModeChange(this.value)">
47
+ <option value="none" selected>none</option>
48
+ <option value="weight">weight</option>
49
+ <option value="leeway">leeway</option>
50
+ <option value="factor">factor</option>
51
+ <option value="trace">trace</option>
39
52
  </select>
40
53
  </div>
41
54
 
42
55
 
56
+
57
+
43
58
  <!-- Pause Button -->
44
59
  <button id="toggle-updates" class="menu-item" onclick="toggleUpdates()">Pause</button>
45
60
 
package/public/main.css CHANGED
@@ -28,6 +28,7 @@
28
28
  color: black;
29
29
  }
30
30
 
31
+
31
32
  /* --- Instance IDs for Specific Colors --- */
32
33
  #heading {
33
34
  color: var(--heading-color);
@@ -86,8 +87,7 @@ th, td {
86
87
  font-size: 11px;
87
88
  }
88
89
  .selectedCell {
89
- color: #fff;
90
- background-color: #1976d2;
90
+ color: rgb(17, 204, 0);
91
91
  }
92
92
  .emptyCell {
93
93
  }
package/CHANGELOG.md DELETED
@@ -1,28 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
-
7
- ## [1.0.1] - 2025-1-27
8
- ### Added
9
- - basic functionality to estimate speed, leeway and current using Kalman filters
10
- - basic functionality for a webapp that shows what is going on
11
-
12
- ## [1.1.0] - 2025-8-20
13
- ### Changed
14
- - Path names for current. It now is self.environment.current.setTrue and self.environment.current.drift
15
- ### Fixed
16
- - Case error in filename that prevented the webapp from displaying the correction table
17
- - Corrected speed through water is now calculated when the correction table is fixed
18
-
19
- ## [1.3.0] - ntb
20
- ### Changed
21
- - Simplified code
22
- - Less CPU usage
23
- ### Added
24
- - Option to set the source for input paths
25
- - Smart heartbeat. Calculations will run right after an update of the path that has the lowest update frequency
26
- - Oversampling for quick update inputs to get better variance
27
- ### Fixed
28
- - Angles are now reported in the appropriate range, 0 to 2 * PI or -PI to PI, depending on path
Binary file
package/currentModel.png DELETED
Binary file
package/models.md DELETED
@@ -1,26 +0,0 @@
1
- ## Correction model
2
- ### raw
3
- \vec{correction} = \mathbb{R}(-heading) \cdot \vec{groundSpeed} - \vec{boatSpeed} - \mathbb{R}(-heading) \cdot \vec{waterSpeed}
4
-
5
- ### link
6
- https://latex.codecogs.com/svg.image?\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}
7
-
8
- ### html
9
- <img src="https://latex.codecogs.com/png.image?\dpi{110}\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}" title="\vec{correction}=\mathbb{R}(-heading)\cdot\vec{groundSpeed}-\vec{boatSpeed}-\mathbb{R}(-heading)\cdot\vec{waterSpeed}" />
10
-
11
- ### image
12
-
13
- ![correction model](correctionModel.png)
14
-
15
- ## current model
16
- ### raw
17
- \vec{waterSpeed} = \vec{groundSpeed} - \mathbb{R}(heading) \cdot(\vec{boatSpeed}+\vec{correction})
18
- ### link
19
- https://latex.codecogs.com/svg.image?\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}&plus;\vec{correction})
20
- ### html
21
- <img src="https://latex.codecogs.com/svg.image?\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}&plus;\vec{correction})" title="\vec{waterSpeed}=\vec{groundSpeed}-\mathbb{R}(heading)\cdot(\vec{boatSpeed}+\vec{correction})" />
22
-
23
-
24
- ### image
25
-
26
- ![correction model](currentModel.png)
@@ -1,78 +0,0 @@
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>Surface Plot</title>
8
- <!-- Correct Plotly.js CDN -->
9
- <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
10
- </head>
11
-
12
- <body>
13
- <div id="surfacePlotX" style="width: 1024px; height: 800px;"></div>
14
- <div id="surfacePlotY" style="width: 1024px; height: 800px;"></div>
15
-
16
- <script>
17
- // Data for the correction table
18
- const x = [0, 5, 10, 15, 20]; // Speed axis
19
- const y = [0, 10, 20, 30, 40]; // Heel axis
20
-
21
- // Correction values for the X surface
22
- const zX = [
23
- [0, 1, 2, 3, 4],
24
- [1, 2, 3, 4, 5],
25
- [2, 3, 4, 5, 6],
26
- [3, 4, 5, 6, 7],
27
- [4, 5, 6, 7, 8],
28
- ];
29
-
30
- // Correction values for the Y surface
31
- const zY = [
32
- [0, -1, -2, -3, -4],
33
- [-1, -2, -3, -4, -5],
34
- [-2, -3, -4, -5, -6],
35
- [-3, -4, -5, -6, -7],
36
- [-4, -5, -6, -7, -8],
37
- ];
38
-
39
- // Surface plot for X correction
40
- const dataX = {
41
- x: x,
42
- y: y,
43
- z: zX,
44
- type: 'surface',
45
- colorscale: 'Viridis',
46
- };
47
-
48
- // Surface plot for Y correction
49
- const dataY = {
50
- x: x,
51
- y: y,
52
- z: zY,
53
- type: 'surface',
54
- colorscale: 'Cividis',
55
- };
56
-
57
- // Render plots
58
- Plotly.newPlot('surfacePlotX', [dataX], {
59
- title: 'X Correction Surface',
60
- scene: {
61
- xaxis: { title: 'Speed' },
62
- yaxis: { title: 'Heel' },
63
- zaxis: { title: 'X Correction' },
64
- },
65
- });
66
-
67
- Plotly.newPlot('surfacePlotY', [dataY], {
68
- title: 'Y Correction Surface',
69
- scene: {
70
- xaxis: { title: 'Speed' },
71
- yaxis: { title: 'Heel' },
72
- zaxis: { title: 'Y Correction' },
73
- },
74
- });
75
- </script>
76
- </body>
77
-
78
- </html>