Files
dog_training/static/docs/dog_training_progress_chart.html

423 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dog Training Progress - Sit Command</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<!--
<script src="https://cdnjs.cloudflare.com/ajax/libs/date-fns/2.29.3/index.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-date-fns/2.0.0/chartjs-adapter-date-fns.bundle.min.js"></script>
-->
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
margin: 0;
padding: 20px;
background-color: #f8fafc;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 24px;
}
.header {
margin-bottom: 24px;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 16px;
}
.header h1 {
margin: 0 0 8px 0;
color: #1e293b;
font-size: 24px;
font-weight: 600;
}
.header p {
margin: 0;
color: #64748b;
font-size: 14px;
}
.chart-container {
position: relative;
height: 500px;
margin-bottom: 24px;
}
.legend-container {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 16px;
padding: 16px;
background-color: #f8fafc;
border-radius: 8px;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: #475569;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 2px;
}
.metrics-summary {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-top: 24px;
}
.metric-card {
background: #f8fafc;
padding: 16px;
border-radius: 8px;
border-left: 4px solid #3b82f6;
}
.metric-label {
font-size: 12px;
color: #64748b;
margin-bottom: 4px;
}
.metric-value {
font-size: 20px;
font-weight: 600;
color: #1e293b;
}
.metric-change {
font-size: 12px;
margin-top: 4px;
}
.positive { color: #059669; }
.negative { color: #dc2626; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Training Progress Report - "Sit" Command</h1>
<p>Tracking compliance duration and response time from April 1st - July 30th, 2025</p>
</div>
<div class="chart-container">
<canvas id="trainingChart"></canvas>
</div>
<div class="legend-container" id="obedienceLegend">
<!-- Legend will be populated by JavaScript -->
</div>
<div class="metrics-summary">
<div class="metric-card">
<div class="metric-label">Average Compliance Duration</div>
<div class="metric-value">65.2s</div>
<div class="metric-change positive">+450% from start</div>
</div>
<div class="metric-card">
<div class="metric-label">Average Response Time</div>
<div class="metric-value">4.3s</div>
<div class="metric-change positive">-64% from start</div>
</div>
<div class="metric-card">
<div class="metric-label">Most Common Obedience Level</div>
<div class="metric-value">After Firm Look</div>
<div class="metric-change">Recent sessions</div>
</div>
<div class="metric-card">
<div class="metric-label">Training Sessions</div>
<div class="metric-value">32</div>
<div class="metric-change">4 months period</div>
</div>
</div>
</div>
<script>
// Define obedience levels and their corresponding colors
const obedienceLevels = [
{ name: "Eager", color: "#16a34a", value: 15 },
{ name: "After Firm Look", color: "#22c55e", value: 14 },
{ name: "After Power Pose", color: "#65a30d", value: 13 },
{ name: "After Threatening to Approach", color: "#84cc16", value: 12 },
{ name: "After Moving a Few Steps", color: "#eab308", value: 11 },
{ name: "After Moving Half Way", color: "#f59e0b", value: 10 },
{ name: "After Approaching Most of The Way", color: "#f97316", value: 9 },
{ name: "After Touching Collar", color: "#ea580c", value: 8 },
{ name: "After Running Away And Returning", color: "#dc2626", value: 7 },
{ name: "After Being Lead by Collar or Lead", color: "#b91c1c", value: 6 },
{ name: "After Check or Bribe", color: "#991b1b", value: 5 },
{ name: "After Multiple Checks and/or Bribes", color: "#7f1d1d", value: 4 },
{ name: "None - Refusal", color: "#6b7280", value: 3 },
{ name: "Refused and Ran Away", color: "#4b5563", value: 2 },
{ name: "Refusing to Return", color: "#374151", value: 1 }
];
// Generate training data from April 1st to July 30th, 2025
function generateTrainingData() {
const data = [];
const startDate = new Date('2025-04-01');
const endDate = new Date('2025-07-30');
let currentDate = new Date(startDate);
let complianceDuration = 10; // Starting at 10 seconds
let responseTime = 12; // Starting at 12 seconds
while (currentDate <= endDate) {
// Add some volatility and general improvement trend
const volatility = (Math.random() - 0.5) * 0.3;
const progressFactor = (currentDate - startDate) / (endDate - startDate);
// Compliance duration: improve from 10s to 120s with volatility
const targetCompliance = 10 + (110 * progressFactor);
complianceDuration = Math.max(5, Math.min(120,
complianceDuration + (targetCompliance - complianceDuration) * 0.1 +
volatility * 15 + (Math.random() - 0.5) * 10
));
// Response time: improve from 12s to 2s with volatility
const targetResponse = 12 - (10 * progressFactor);
responseTime = Math.max(1, Math.min(15,
responseTime + (targetResponse - responseTime) * 0.1 +
volatility * 2 + (Math.random() - 0.5) * 2
));
// Determine obedience level based on performance
let obedienceLevel;
if (complianceDuration > 90 && responseTime < 3) {
obedienceLevel = obedienceLevels[0]; // Eager
} else if (complianceDuration > 70 && responseTime < 4) {
obedienceLevel = obedienceLevels[1]; // After Firm Look
} else if (complianceDuration > 50 && responseTime < 5) {
obedienceLevel = obedienceLevels[2]; // After Power Pose
} else if (complianceDuration > 40 && responseTime < 6) {
obedienceLevel = obedienceLevels[3]; // After Threatening to Approach
} else if (complianceDuration > 30 && responseTime < 7) {
obedienceLevel = obedienceLevels[4]; // After Moving a Few Steps
} else if (complianceDuration > 25 && responseTime < 8) {
obedienceLevel = obedienceLevels[5]; // After Moving Half Way
} else if (complianceDuration > 20 && responseTime < 9) {
obedienceLevel = obedienceLevels[6]; // After Approaching Most of The Way
} else if (complianceDuration > 15 && responseTime < 10) {
obedienceLevel = obedienceLevels[7]; // After Touching Collar
} else if (complianceDuration > 10 && responseTime < 11) {
obedienceLevel = obedienceLevels[8]; // After Running Away And Returning
} else if (complianceDuration > 8) {
obedienceLevel = obedienceLevels[9]; // After Being Lead by Collar or Lead
} else if (complianceDuration > 5) {
obedienceLevel = obedienceLevels[10]; // After Check or Bribe
} else if (complianceDuration > 3) {
obedienceLevel = obedienceLevels[11]; // After Multiple Checks and/or Bribes
} else if (complianceDuration > 1) {
obedienceLevel = obedienceLevels[12]; // None - Refusal
} else if (responseTime > 12) {
obedienceLevel = obedienceLevels[13]; // Refused and Ran Away
} else {
obedienceLevel = obedienceLevels[14]; // Refusing to Return
}
data.push({
date: new Date(currentDate),
complianceDuration: Math.round(complianceDuration * 10) / 10,
responseTime: Math.round(responseTime * 10) / 10,
obedienceLevel: obedienceLevel
});
// Advance date by 2-4 days randomly
const daysToAdd = Math.floor(Math.random() * 3) + 2;
currentDate.setDate(currentDate.getDate() + daysToAdd);
}
return data;
}
// Generate the training data
const trainingData = generateTrainingData();
// Create legend
function createLegend() {
const legendContainer = document.getElementById('obedienceLegend');
obedienceLevels.forEach(level => {
const legendItem = document.createElement('div');
legendItem.className = 'legend-item';
legendItem.innerHTML = `
<div class="legend-color" style="background-color: ${level.color}"></div>
<span>${level.name}</span>
`;
legendContainer.appendChild(legendItem);
});
}
// Wait for Chart.js to load and initialise
function initializeChart() {
if (typeof Chart === 'undefined') {
console.error('Chart.js not loaded');
document.querySelector('.chart-container').innerHTML = '<p style="text-align: center; color: #dc2626; padding: 50px;">Error loading Chart.js library. Please refresh the page.</p>';
return;
}
const ctx = document.getElementById('trainingChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
datasets: [
{
label: 'Compliance Duration (seconds)',
data: trainingData.map(d => ({
x: d.date,
y: d.complianceDuration
})),
pointBackgroundColor: trainingData.map(d => d.obedienceLevel.color),
pointBorderColor: trainingData.map(d => d.obedienceLevel.color),
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderColor: '#3b82f6',
borderWidth: 2,
pointRadius: 6,
pointHoverRadius: 8,
yAxisID: 'y',
tension: 0.1
},
{
label: 'Response Time (seconds)',
data: trainingData.map(d => ({
x: d.date,
y: d.responseTime
})),
pointBackgroundColor: trainingData.map(d => d.obedienceLevel.color),
pointBorderColor: trainingData.map(d => d.obedienceLevel.color),
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderColor: '#ef4444',
borderWidth: 2,
pointRadius: 6,
pointHoverRadius: 8,
yAxisID: 'y1',
tension: 0.1
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
title: {
display: true,
text: 'Sit Command Training Progress Over Time',
font: {
size: 16,
weight: 'bold'
},
padding: 20
},
legend: {
display: true,
position: 'top',
labels: {
usePointStyle: true,
padding: 20
}
},
tooltip: {
callbacks: {
afterBody: function(context) {
const dataIndex = context[0].dataIndex;
const obedienceLevel = trainingData[dataIndex].obedienceLevel.name;
return `Obedience Level: ${obedienceLevel}`;
}
}
}
},
scales: {
x: {
type: 'linear',
ticks: {
callback: function(value, index) {
const date = trainingData[index]?.date;
if (date) {
return date.toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short'
});
}
return value;
}
},
title: {
display: true,
text: 'Training Session',
font: {
weight: 'bold'
}
},
grid: {
color: '#e2e8f0'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Compliance Duration (seconds)',
color: '#3b82f6',
font: {
weight: 'bold'
}
},
grid: {
color: '#f1f5f9'
},
ticks: {
color: '#3b82f6'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Response Time (seconds)',
color: '#ef4444',
font: {
weight: 'bold'
}
},
grid: {
drawOnChartArea: false,
},
ticks: {
color: '#ef4444'
}
}
}
}
});
}
// Create the legend
createLegend();
</script>
</body>
</html>