Initial commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user