|
1 | 1 | 'use strict'; |
2 | 2 |
|
3 | 3 | import xml2js from 'xml2js'; |
| 4 | +import { Chart, registerables } from 'chart.js'; |
| 5 | + |
| 6 | +// Register Chart.js components |
| 7 | +Chart.register(...registerables); |
| 8 | + |
| 9 | +// Make Chart available globally for plotElevation function |
| 10 | +window.Chart = Chart; |
4 | 11 |
|
5 | 12 | import Map from 'ol/Map.js'; |
6 | 13 | import XYZ from 'ol/source/XYZ.js'; |
@@ -2333,6 +2340,9 @@ function iconKey(filename) { |
2333 | 2340 |
|
2334 | 2341 | plotElevation(); |
2335 | 2342 | })() |
| 2343 | + } else { |
| 2344 | + // Update elevation chart even for non-selected waypoints |
| 2345 | + plotElevation(); |
2336 | 2346 | } |
2337 | 2347 | } |
2338 | 2348 | else if (tempMarker.kind == "home" ) { |
@@ -4221,131 +4231,197 @@ function iconKey(filename) { |
4221 | 4231 | return altitude; |
4222 | 4232 | } |
4223 | 4233 |
|
| 4234 | + // Track elevation chart update sequence to prevent race conditions |
| 4235 | + let elevationUpdateSequence = 0; |
| 4236 | + |
4224 | 4237 | function plotElevation() { |
4225 | | - /* |
4226 | 4238 | if ($('#missionPlannerElevation').is(":visible") && !disableMarkerEdit) { |
4227 | 4239 | if (mission.isEmpty()) { |
4228 | | - var data = [[0], [0]]; |
4229 | | - var layout = {showlegend: true, |
4230 | | - legend: { |
4231 | | - "orientation": "h", |
4232 | | - xanchor: "center", |
4233 | | - y: 1.3, |
4234 | | - x: 0.5 |
4235 | | - }, |
4236 | | - title: 'Mission Elevation Profile', |
4237 | | - xaxis: { |
4238 | | - title: 'Distance (m)' |
4239 | | - }, |
4240 | | - yaxis: { |
4241 | | - title: 'Elevation (m)', |
4242 | | - }, |
4243 | | - height: 300, |
4244 | | - } |
4245 | | - //Plotly.newPlot('elevationDiv', data, layout); |
4246 | | -
|
4247 | | - var ctx = $("#elevationChart").get(0); |
4248 | | -
|
4249 | | - new Chart(ctx, { |
| 4240 | + const ctx = $("#elevationChart").get(0); |
| 4241 | + |
| 4242 | + if (!ctx || ctx.tagName !== 'CANVAS') { |
| 4243 | + console.error('elevationChart canvas element not found'); |
| 4244 | + return; |
| 4245 | + } |
| 4246 | + |
| 4247 | + // Destroy existing chart if it exists |
| 4248 | + if (window.elevationChartInstance) { |
| 4249 | + window.elevationChartInstance.destroy(); |
| 4250 | + window.elevationChartInstance = null; |
| 4251 | + } |
| 4252 | + |
| 4253 | + // Create empty chart with message |
| 4254 | + window.elevationChartInstance = new Chart(ctx, { |
4250 | 4255 | type: 'line', |
4251 | 4256 | data: { |
4252 | | - labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'], |
4253 | | - datasets: [ |
4254 | | - { |
4255 | | - label: 'One', |
4256 | | - data: [12, 19, 3, 5, 2, 3], |
4257 | | - borderWidth: 1, |
4258 | | - fill: 'start', |
4259 | | - }, |
4260 | | - { |
4261 | | - label: 'Two', |
4262 | | - data: [13, 21, 7, 7, 3, 6], |
4263 | | - borderWidth: 2, |
4264 | | - radius: 0 |
4265 | | - } |
4266 | | - ] |
| 4257 | + labels: [0], |
| 4258 | + datasets: [ |
| 4259 | + { |
| 4260 | + label: 'WGS84 elevation', |
| 4261 | + data: [{x: 0, y: 0}], |
| 4262 | + borderColor: '#ff7f0e', |
| 4263 | + backgroundColor: 'rgba(255, 127, 14, 0.2)', |
| 4264 | + borderWidth: 2, |
| 4265 | + fill: true, |
| 4266 | + pointRadius: 0, |
| 4267 | + }, |
| 4268 | + { |
| 4269 | + label: 'Mission altitude', |
| 4270 | + data: [{x: 0, y: 0}], |
| 4271 | + borderColor: '#1497f1', |
| 4272 | + backgroundColor: 'rgba(20, 151, 241, 0)', |
| 4273 | + borderWidth: 2, |
| 4274 | + pointRadius: 5, |
| 4275 | + pointBackgroundColor: '#1f77b4', |
| 4276 | + } |
| 4277 | + ] |
4267 | 4278 | }, |
4268 | 4279 | options: { |
| 4280 | + responsive: true, |
4269 | 4281 | maintainAspectRatio: false, |
| 4282 | + plugins: { |
| 4283 | + title: { |
| 4284 | + display: true, |
| 4285 | + text: 'Mission Elevation Profile' |
| 4286 | + }, |
| 4287 | + legend: { |
| 4288 | + display: true, |
| 4289 | + position: 'top', |
| 4290 | + } |
| 4291 | + }, |
4270 | 4292 | scales: { |
4271 | | - y: { |
4272 | | - beginAtZero: true |
| 4293 | + x: { |
| 4294 | + type: 'linear', |
| 4295 | + title: { |
| 4296 | + display: true, |
| 4297 | + text: 'Distance (m)' |
| 4298 | + } |
| 4299 | + }, |
| 4300 | + y: { |
| 4301 | + title: { |
| 4302 | + display: true, |
| 4303 | + text: 'Elevation (m)' |
| 4304 | + }, |
| 4305 | + beginAtZero: true |
| 4306 | + } |
4273 | 4307 | } |
4274 | | - } |
4275 | 4308 | } |
4276 | | - }); |
| 4309 | + }); |
4277 | 4310 | } |
4278 | 4311 | else { |
4279 | 4312 | (async () => { |
4280 | | - const [lengthMission, totalMissionDistance, samples, elevation, altPoint2measure, namePoint2measure, refPoint2measure] = await mission.getElevation(globalSettings); |
4281 | | - let x_elevation = Array.from(Array(samples+1), (_,i)=> i*totalMissionDistance/samples); |
4282 | | - var trace_WGS84 = { |
4283 | | - x: x_elevation, |
4284 | | - y: elevation, |
4285 | | - type: 'scatter', |
4286 | | - name: 'WGS84 elevation', |
4287 | | - hovertemplate: '<b>Elevation</b>: %{y} m', |
4288 | | - fill: 'tozeroy', |
4289 | | - line: { |
4290 | | - color: '#ff7f0e', |
4291 | | - }, |
4292 | | - }; |
4293 | | - let y_missionElevation = altPoint2measure.map((x,i) => x / 100 + HOME.getAlt()*(1-refPoint2measure[i])); |
4294 | | - let y_elevationReference = refPoint2measure.map((x,i) => (x == 1 ? "WGS84" : "Take-off Home")); |
4295 | | - var trace_missionHeight = { |
4296 | | - x: lengthMission, |
4297 | | - y: y_missionElevation , |
4298 | | - type: 'scatter', |
4299 | | - mode: 'lines+markers+text', |
4300 | | - name: 'Mission altitude', |
4301 | | - text: namePoint2measure, |
4302 | | - textposition: 'top center', |
4303 | | - textfont: { |
4304 | | - family: 'Raleway, sans-serif' |
4305 | | - }, |
4306 | | - customdata: y_elevationReference, |
4307 | | - hovertemplate: '<b>WP</b>: %{text}' + |
4308 | | - '<br><b>Elevation</b>: %{y} m<br>' + |
4309 | | - '<b>Reference</b>: %{customdata}', |
4310 | | - line: { |
4311 | | - color: '#1497f1', |
4312 | | - }, |
4313 | | - marker: { |
4314 | | - color: '#1f77b4', |
4315 | | - }, |
4316 | | - }; |
4317 | | - Show multi mission number in plot title when single mission displayed |
4318 | | - * Not updated when ALL multi missions displayed since plot disabled |
4319 | | - |
4320 | | - let missionNumber = ''; |
4321 | | - if (multimissionCount) { |
4322 | | - missionNumber = ' ' + ($('#multimissionOptionList').val()); |
| 4313 | + // Capture current sequence number to detect stale updates |
| 4314 | + const currentSequence = ++elevationUpdateSequence; |
| 4315 | + |
| 4316 | + try { |
| 4317 | + const [lengthMission, totalMissionDistance, samples, elevation, altPoint2measure, namePoint2measure, refPoint2measure] = await mission.getElevation(globalSettings); |
| 4318 | + |
| 4319 | + // Check if a newer update has been triggered while we were fetching data |
| 4320 | + if (currentSequence !== elevationUpdateSequence) { |
| 4321 | + console.log('Ignoring stale elevation data'); |
| 4322 | + return; |
| 4323 | + } |
| 4324 | + const x_elevation = Array.from(Array(samples+1), (_,i)=> i*totalMissionDistance/samples); |
| 4325 | + const y_missionElevation = altPoint2measure.map((x,i) => x / 100 + HOME.getAlt()*(1-refPoint2measure[i])); |
| 4326 | + |
| 4327 | + /* Show multi mission number in plot title when single mission displayed |
| 4328 | + * Not updated when ALL multi missions displayed since plot disabled |
| 4329 | + */ |
| 4330 | + let missionNumber = ''; |
| 4331 | + if (multimissionCount) { |
| 4332 | + missionNumber = ' ' + ($('#multimissionOptionList').val()); |
| 4333 | + } |
| 4334 | + const chartTitle = 'Mission' + missionNumber + ' Elevation Profile'; |
| 4335 | + |
| 4336 | + // Calculate Y-axis range safely |
| 4337 | + const minElevation = elevation.length > 0 ? Math.min(...elevation) : 0; |
| 4338 | + const minMission = y_missionElevation.length > 0 ? Math.min(...y_missionElevation) : 0; |
| 4339 | + const maxElevation = elevation.length > 0 ? Math.max(...elevation) : 100; |
| 4340 | + const maxMission = y_missionElevation.length > 0 ? Math.max(...y_missionElevation) : 100; |
| 4341 | + |
| 4342 | + const ctx = $("#elevationChart").get(0); |
| 4343 | + if (!ctx || ctx.tagName !== 'CANVAS') { |
| 4344 | + console.error('elevationChart canvas element not found'); |
| 4345 | + return; |
| 4346 | + } |
| 4347 | + |
| 4348 | + const newData = { |
| 4349 | + labels: x_elevation, |
| 4350 | + datasets: [ |
| 4351 | + { |
| 4352 | + label: 'WGS84 elevation', |
| 4353 | + data: elevation.map((y, i) => ({x: x_elevation[i], y: y})), |
| 4354 | + borderColor: '#ff7f0e', |
| 4355 | + backgroundColor: 'rgba(255, 127, 14, 0.2)', |
| 4356 | + borderWidth: 2, |
| 4357 | + fill: true, |
| 4358 | + pointRadius: 0, |
| 4359 | + }, |
| 4360 | + { |
| 4361 | + label: 'Mission altitude', |
| 4362 | + data: lengthMission.map((x, i) => ({x: x, y: y_missionElevation[i]})), |
| 4363 | + borderColor: '#1497f1', |
| 4364 | + backgroundColor: 'rgba(20, 151, 241, 0)', |
| 4365 | + borderWidth: 2, |
| 4366 | + pointRadius: 5, |
| 4367 | + pointBackgroundColor: '#1f77b4', |
| 4368 | + } |
| 4369 | + ] |
| 4370 | + }; |
| 4371 | + |
| 4372 | + // Update existing chart if it exists, otherwise create new one |
| 4373 | + if (window.elevationChartInstance) { |
| 4374 | + // Update data |
| 4375 | + window.elevationChartInstance.data = newData; |
| 4376 | + window.elevationChartInstance.options.plugins.title.text = chartTitle; |
| 4377 | + window.elevationChartInstance.options.scales.y.min = Math.floor(-10 + Math.min(minMission, minElevation)); |
| 4378 | + window.elevationChartInstance.options.scales.y.max = Math.ceil(10 + Math.max(maxMission, maxElevation)); |
| 4379 | + // Trigger re-render without animation for better performance during drag operations |
| 4380 | + window.elevationChartInstance.update('none'); |
| 4381 | + } else { |
| 4382 | + // Create new chart |
| 4383 | + window.elevationChartInstance = new Chart(ctx, { |
| 4384 | + type: 'line', |
| 4385 | + data: newData, |
| 4386 | + options: { |
| 4387 | + responsive: true, |
| 4388 | + maintainAspectRatio: false, |
| 4389 | + plugins: { |
| 4390 | + title: { |
| 4391 | + display: true, |
| 4392 | + text: chartTitle |
| 4393 | + }, |
| 4394 | + legend: { |
| 4395 | + display: true, |
| 4396 | + position: 'top', |
| 4397 | + } |
| 4398 | + }, |
| 4399 | + scales: { |
| 4400 | + x: { |
| 4401 | + type: 'linear', |
| 4402 | + title: { |
| 4403 | + display: true, |
| 4404 | + text: 'Distance (m)' |
| 4405 | + } |
| 4406 | + }, |
| 4407 | + y: { |
| 4408 | + title: { |
| 4409 | + display: true, |
| 4410 | + text: 'Elevation (m)' |
| 4411 | + }, |
| 4412 | + min: Math.floor(-10 + Math.min(minMission, minElevation)), |
| 4413 | + max: Math.ceil(10 + Math.max(maxMission, maxElevation)) |
| 4414 | + } |
| 4415 | + } |
| 4416 | + } |
| 4417 | + }); |
| 4418 | + } |
| 4419 | + } catch (error) { |
| 4420 | + console.error('Failed to plot elevation:', error); |
4323 | 4421 | } |
4324 | | - var layout = {showlegend: true, |
4325 | | - legend: { |
4326 | | - "orientation": "h", |
4327 | | - xanchor: "center", |
4328 | | - y: 1.3, |
4329 | | - x: 0.5 |
4330 | | - }, |
4331 | | - title: 'Mission' + missionNumber + ' Elevation Profile', |
4332 | | - xaxis: { |
4333 | | - title: 'Distance (m)' |
4334 | | - }, |
4335 | | - yaxis: { |
4336 | | - title: 'Elevation (m)', |
4337 | | - range: [-10 + Math.min(Math.min(...y_missionElevation), Math.min(...elevation)), 10 + Math.max(Math.max(...y_missionElevation), Math.max(...elevation))], |
4338 | | - }, |
4339 | | - height: 300, |
4340 | | - } |
4341 | | -
|
4342 | | - var data = [trace_WGS84, trace_missionHeight]; |
4343 | | -
|
4344 | | - //Plotly.newPlot('elevationDiv', data, layout); |
4345 | 4422 | })() |
4346 | 4423 | } |
4347 | 4424 | } |
4348 | | - */ |
4349 | 4425 | } |
4350 | 4426 |
|
4351 | 4427 | function parseBooleans (str) { |
|
0 commit comments