Initial commit

This commit is contained in:
2026-07-03 16:37:12 +07:00
commit 63b8c1ea8b
1931 changed files with 640587 additions and 0 deletions

View File

@@ -0,0 +1,379 @@
// Velocity Chart Renderer
// Renders line charts for velocity data comparison (OdomVel vs CmdVel)
window.velocityChartRenderer = {
linearChartCtx: null,
angularChartCtx: null,
linearChartData: { odom: [], cmd: [], times: [] },
angularChartData: { odom: [], cmd: [], times: [] },
initLinearVelocityChart: function(canvas) {
if (!canvas || typeof canvas.getContext !== 'function') return;
try {
this.linearChartCtx = canvas.getContext('2d');
this.linearChartData = { odom: [], cmd: [], times: [] };
} catch (e) {
console.error('Error initializing linear velocity chart:', e);
}
},
initAngularVelocityChart: function(canvas) {
if (!canvas || typeof canvas.getContext !== 'function') return;
try {
this.angularChartCtx = canvas.getContext('2d');
this.angularChartData = { odom: [], cmd: [], times: [] };
} catch (e) {
console.error('Error initializing angular velocity chart:', e);
}
},
updateLinearVelocityChart: function(canvas, data) {
if (!canvas || !data || data.length === 0) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = canvas.offsetHeight || 150;
// Clear canvas
ctx.clearRect(0, 0, width, height);
if (data.length === 0) return;
// Calculate min/max values for scaling
let dataMinVal = Math.min(...data.map(d => Math.min(d.odomVel, d.cmdVel)));
let dataMaxVal = Math.max(...data.map(d => Math.max(d.odomVel, d.cmdVel)));
// Set grid spacing to 0.1 m/s
const gridStep = 0.1;
// Round min/max to nearest 0.1 and add padding
let minVal = Math.floor(dataMinVal / gridStep) * gridStep;
let maxVal = Math.ceil(dataMaxVal / gridStep) * gridStep;
// Ensure at least some range
if (minVal === maxVal) {
minVal -= gridStep;
maxVal += gridStep;
}
// Add extra padding
minVal -= gridStep;
maxVal += gridStep;
// Calculate time range
const maxTime = Math.max(...data.map(d => d.time));
const minTime = Math.min(...data.map(d => d.time));
const timeRange = maxTime - minTime || 1;
// Padding
const padding = { top: 20, right: 20, bottom: 30, left: 50 };
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;
// Draw axes
ctx.strokeStyle = '#666';
ctx.lineWidth = 1;
// X axis
ctx.beginPath();
ctx.moveTo(padding.left, height - padding.bottom);
ctx.lineTo(width - padding.right, height - padding.bottom);
ctx.stroke();
// Y axis
ctx.beginPath();
ctx.moveTo(padding.left, padding.top);
ctx.lineTo(padding.left, height - padding.bottom);
ctx.stroke();
// Draw grid lines with 0.1 m/s spacing
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 0.5;
// Horizontal grid lines at 0.1 m/s intervals
const valueRange = maxVal - minVal;
const numSteps = Math.round(valueRange / gridStep);
// Determine label step based on range to avoid overcrowding
// If range is large, show labels less frequently
let labelStep = 1; // Show every label by default
if (valueRange > 1.0) {
labelStep = 2; // Show every 2nd label if range > 1.0
}
if (valueRange > 2.0) {
labelStep = 5; // Show every 5th label if range > 2.0
}
if (valueRange > 5.0) {
labelStep = 10; // Show every 10th label if range > 5.0
}
for (let i = 0; i <= numSteps; i++) {
const value = minVal + i * gridStep;
const y = padding.top + chartHeight - ((value - minVal) / valueRange) * chartHeight;
ctx.beginPath();
ctx.moveTo(padding.left, y);
ctx.lineTo(width - padding.right, y);
ctx.stroke();
// Y axis labels - only show if it's a multiple of labelStep
if (i % labelStep === 0) {
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'right';
ctx.fillText(value.toFixed(2), padding.left - 5, y + 3);
}
}
// Vertical grid lines (time)
const xSteps = 5;
for (let i = 0; i <= xSteps; i++) {
const x = padding.left + (chartWidth / xSteps) * i;
ctx.beginPath();
ctx.moveTo(x, padding.top);
ctx.lineTo(x, height - padding.bottom);
ctx.stroke();
// X axis labels
const time = minTime + (timeRange / xSteps) * i;
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'center';
ctx.fillText(time.toFixed(1) + 's', x, height - padding.bottom + 15);
}
// Draw OdomVel line (blue - #2196f3)
if (data.length >= 1) {
ctx.strokeStyle = '#2196f3'; // Blue
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#2196f3';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
// Draw CmdVel line (green - #4caf50)
if (data.length >= 1) {
ctx.strokeStyle = '#4caf50'; // Green
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#4caf50';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
},
updateAngularVelocityChart: function(canvas, data) {
if (!canvas || !data || data.length === 0) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const width = canvas.width = canvas.offsetWidth;
const height = canvas.height = canvas.offsetHeight || 150;
// Clear canvas
ctx.clearRect(0, 0, width, height);
if (data.length === 0) return;
// Calculate min/max values for scaling
let dataMinVal = Math.min(...data.map(d => Math.min(d.odomVel, d.cmdVel)));
let dataMaxVal = Math.max(...data.map(d => Math.max(d.odomVel, d.cmdVel)));
// Set grid spacing to 0.1 rad/s
const gridStep = 0.1;
// Round min/max to nearest 0.1 and add padding
let minVal = Math.floor(dataMinVal / gridStep) * gridStep;
let maxVal = Math.ceil(dataMaxVal / gridStep) * gridStep;
// Ensure at least some range
if (minVal === maxVal) {
minVal -= gridStep;
maxVal += gridStep;
}
// Add extra padding
minVal -= gridStep;
maxVal += gridStep;
// Calculate time range
const maxTime = Math.max(...data.map(d => d.time));
const minTime = Math.min(...data.map(d => d.time));
const timeRange = maxTime - minTime || 1;
// Padding
const padding = { top: 20, right: 20, bottom: 30, left: 50 };
const chartWidth = width - padding.left - padding.right;
const chartHeight = height - padding.top - padding.bottom;
// Draw axes
ctx.strokeStyle = '#666';
ctx.lineWidth = 1;
// X axis
ctx.beginPath();
ctx.moveTo(padding.left, height - padding.bottom);
ctx.lineTo(width - padding.right, height - padding.bottom);
ctx.stroke();
// Y axis
ctx.beginPath();
ctx.moveTo(padding.left, padding.top);
ctx.lineTo(padding.left, height - padding.bottom);
ctx.stroke();
// Draw grid lines with 0.1 rad/s spacing
ctx.strokeStyle = '#ddd';
ctx.lineWidth = 0.5;
// Horizontal grid lines at 0.1 rad/s intervals
const valueRange = maxVal - minVal;
const numSteps = Math.round(valueRange / gridStep);
// Determine label step based on range to avoid overcrowding
// If range is large, show labels less frequently
let labelStep = 1; // Show every label by default
if (valueRange > 1.0) {
labelStep = 2; // Show every 2nd label if range > 1.0
}
if (valueRange > 2.0) {
labelStep = 5; // Show every 5th label if range > 2.0
}
if (valueRange > 5.0) {
labelStep = 10; // Show every 10th label if range > 5.0
}
for (let i = 0; i <= numSteps; i++) {
const value = minVal + i * gridStep;
const y = padding.top + chartHeight - ((value - minVal) / valueRange) * chartHeight;
ctx.beginPath();
ctx.moveTo(padding.left, y);
ctx.lineTo(width - padding.right, y);
ctx.stroke();
// Y axis labels - only show if it's a multiple of labelStep
if (i % labelStep === 0) {
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'right';
ctx.fillText(value.toFixed(1), padding.left - 5, y + 3);
}
}
// Vertical grid lines (time)
const xSteps = 5;
for (let i = 0; i <= xSteps; i++) {
const x = padding.left + (chartWidth / xSteps) * i;
ctx.beginPath();
ctx.moveTo(x, padding.top);
ctx.lineTo(x, height - padding.bottom);
ctx.stroke();
// X axis labels
const time = minTime + (timeRange / xSteps) * i;
ctx.fillStyle = '#666';
ctx.font = '10px Arial';
ctx.textAlign = 'center';
ctx.fillText(time.toFixed(1) + 's', x, height - padding.bottom + 15);
}
// Draw OdomVel line (blue - #2196f3)
if (data.length >= 1) {
ctx.strokeStyle = '#2196f3'; // Blue
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].odomVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#2196f3';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
// Draw CmdVel line (green - #4caf50)
if (data.length >= 1) {
ctx.strokeStyle = '#4caf50'; // Green
ctx.lineWidth = 2;
if (data.length > 1) {
ctx.beginPath();
for (let i = 0; i < data.length; i++) {
const x = padding.left + ((data[i].time - minTime) / timeRange) * chartWidth;
const y = padding.top + chartHeight - ((data[i].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
// Draw point at current position
const lastX = padding.left + ((data[data.length - 1].time - minTime) / timeRange) * chartWidth;
const lastY = padding.top + chartHeight - ((data[data.length - 1].cmdVel - minVal) / (maxVal - minVal)) * chartHeight;
ctx.fillStyle = '#4caf50';
ctx.beginPath();
ctx.arc(lastX, lastY, 3, 0, 2 * Math.PI);
ctx.fill();
}
}
};