Spaces:
Running
Running
calories
Browse files- index.html +7 -1
- src/components/charts.ts +19 -0
- src/main.ts +7 -0
- src/types/index.ts +1 -0
- src/utils/csvParser.ts +11 -0
index.html
CHANGED
|
@@ -56,11 +56,17 @@
|
|
| 56 |
</div>
|
| 57 |
|
| 58 |
<div class="chart-container">
|
| 59 |
-
<h2
|
| 60 |
<div id="tss-target" class="target-info"></div>
|
| 61 |
<canvas id="tss-chart"></canvas>
|
| 62 |
</div>
|
| 63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
<div class="legend">
|
| 65 |
<div class="legend-item">
|
| 66 |
<span class="legend-color low"></span>
|
|
|
|
| 56 |
</div>
|
| 57 |
|
| 58 |
<div class="chart-container">
|
| 59 |
+
<h2>π₯΅ TSS-based ACWR</h2>
|
| 60 |
<div id="tss-target" class="target-info"></div>
|
| 61 |
<canvas id="tss-chart"></canvas>
|
| 62 |
</div>
|
| 63 |
|
| 64 |
+
<div class="chart-container">
|
| 65 |
+
<h2>π Calories-based ACWR</h2>
|
| 66 |
+
<div id="calories-target" class="target-info"></div>
|
| 67 |
+
<canvas id="calories-chart"></canvas>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
<div class="legend">
|
| 71 |
<div class="legend-item">
|
| 72 |
<span class="legend-color low"></span>
|
src/components/charts.ts
CHANGED
|
@@ -7,6 +7,7 @@ Chart.register(...registerables);
|
|
| 7 |
let distanceChart: Chart | null = null;
|
| 8 |
let durationChart: Chart | null = null;
|
| 9 |
let tssChart: Chart | null = null;
|
|
|
|
| 10 |
|
| 11 |
// ACWR color zones
|
| 12 |
function getACWRColor(value: number | null): string {
|
|
@@ -327,6 +328,20 @@ export function createTSSChart(data: MetricACWRData): void {
|
|
| 327 |
updateTargetInfo('tss-target', data.targetTomorrowValue, 'TSS', data.targetACWR);
|
| 328 |
}
|
| 329 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
function updateTargetInfo(elementId: string, targetValue: number | null | undefined, unit: string, targetACWR: number | undefined): void {
|
| 331 |
const element = document.getElementById(elementId);
|
| 332 |
if (!element) return;
|
|
@@ -354,4 +369,8 @@ export function destroyAllCharts(): void {
|
|
| 354 |
tssChart.destroy();
|
| 355 |
tssChart = null;
|
| 356 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
}
|
|
|
|
| 7 |
let distanceChart: Chart | null = null;
|
| 8 |
let durationChart: Chart | null = null;
|
| 9 |
let tssChart: Chart | null = null;
|
| 10 |
+
let caloriesChart: Chart | null = null;
|
| 11 |
|
| 12 |
// ACWR color zones
|
| 13 |
function getACWRColor(value: number | null): string {
|
|
|
|
| 328 |
updateTargetInfo('tss-target', data.targetTomorrowValue, 'TSS', data.targetACWR);
|
| 329 |
}
|
| 330 |
|
| 331 |
+
export function createCaloriesChart(data: MetricACWRData): void {
|
| 332 |
+
if (caloriesChart) {
|
| 333 |
+
caloriesChart.destroy();
|
| 334 |
+
}
|
| 335 |
+
caloriesChart = createDualAxisChart(
|
| 336 |
+
'calories-chart',
|
| 337 |
+
data,
|
| 338 |
+
'Calories',
|
| 339 |
+
'(kcal)',
|
| 340 |
+
'rgba(234, 179, 8, 0.8)'
|
| 341 |
+
);
|
| 342 |
+
updateTargetInfo('calories-target', data.targetTomorrowValue, 'kcal', data.targetACWR);
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
function updateTargetInfo(elementId: string, targetValue: number | null | undefined, unit: string, targetACWR: number | undefined): void {
|
| 346 |
const element = document.getElementById(elementId);
|
| 347 |
if (!element) return;
|
|
|
|
| 369 |
tssChart.destroy();
|
| 370 |
tssChart = null;
|
| 371 |
}
|
| 372 |
+
if (caloriesChart) {
|
| 373 |
+
caloriesChart.destroy();
|
| 374 |
+
caloriesChart = null;
|
| 375 |
+
}
|
| 376 |
}
|
src/main.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
| 6 |
createDistanceChart,
|
| 7 |
createDurationChart,
|
| 8 |
createTSSChart,
|
|
|
|
| 9 |
destroyAllCharts,
|
| 10 |
} from './components/charts';
|
| 11 |
|
|
@@ -192,6 +193,11 @@ function renderCharts(activities: Activity[]): void {
|
|
| 192 |
(activity) => activity.trainingStressScore,
|
| 193 |
dateRange
|
| 194 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
// Destroy existing charts
|
| 197 |
destroyAllCharts();
|
|
@@ -200,6 +206,7 @@ function renderCharts(activities: Activity[]): void {
|
|
| 200 |
createDistanceChart(distanceData);
|
| 201 |
createDurationChart(durationData);
|
| 202 |
createTSSChart(tssData);
|
|
|
|
| 203 |
}
|
| 204 |
|
| 205 |
// Initialize
|
|
|
|
| 6 |
createDistanceChart,
|
| 7 |
createDurationChart,
|
| 8 |
createTSSChart,
|
| 9 |
+
createCaloriesChart,
|
| 10 |
destroyAllCharts,
|
| 11 |
} from './components/charts';
|
| 12 |
|
|
|
|
| 193 |
(activity) => activity.trainingStressScore,
|
| 194 |
dateRange
|
| 195 |
);
|
| 196 |
+
const caloriesData = calculateMetricACWR(
|
| 197 |
+
activities,
|
| 198 |
+
(activity) => activity.calories,
|
| 199 |
+
dateRange
|
| 200 |
+
);
|
| 201 |
|
| 202 |
// Destroy existing charts
|
| 203 |
destroyAllCharts();
|
|
|
|
| 206 |
createDistanceChart(distanceData);
|
| 207 |
createDurationChart(durationData);
|
| 208 |
createTSSChart(tssData);
|
| 209 |
+
createCaloriesChart(caloriesData);
|
| 210 |
}
|
| 211 |
|
| 212 |
// Initialize
|
src/types/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface Activity {
|
|
| 4 |
distance?: number; // in kilometers
|
| 5 |
duration?: number; // in minutes
|
| 6 |
trainingStressScore?: number;
|
|
|
|
| 7 |
}
|
| 8 |
|
| 9 |
export interface ProcessedData {
|
|
|
|
| 4 |
distance?: number; // in kilometers
|
| 5 |
duration?: number; // in minutes
|
| 6 |
trainingStressScore?: number;
|
| 7 |
+
calories?: number;
|
| 8 |
}
|
| 9 |
|
| 10 |
export interface ProcessedData {
|
src/utils/csvParser.ts
CHANGED
|
@@ -117,12 +117,23 @@ export function parseCSV(file: File, userFTP: number = 343): Promise<Activity[]>
|
|
| 117 |
// Get activity type
|
| 118 |
const activityType = row['Activity Type'] || row['Type'] || row['Sport'];
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
const activity: Activity = {
|
| 121 |
date,
|
| 122 |
activityType,
|
| 123 |
distance,
|
| 124 |
duration,
|
| 125 |
trainingStressScore,
|
|
|
|
| 126 |
};
|
| 127 |
|
| 128 |
return activity;
|
|
|
|
| 117 |
// Get activity type
|
| 118 |
const activityType = row['Activity Type'] || row['Type'] || row['Sport'];
|
| 119 |
|
| 120 |
+
// Parse calories
|
| 121 |
+
const caloriesStr = row['Calories'];
|
| 122 |
+
let calories: number | undefined;
|
| 123 |
+
if (caloriesStr && caloriesStr !== '--') {
|
| 124 |
+
const parsed = parseFloat(caloriesStr);
|
| 125 |
+
if (!isNaN(parsed) && parsed > 0) {
|
| 126 |
+
calories = parsed;
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
const activity: Activity = {
|
| 131 |
date,
|
| 132 |
activityType,
|
| 133 |
distance,
|
| 134 |
duration,
|
| 135 |
trainingStressScore,
|
| 136 |
+
calories,
|
| 137 |
};
|
| 138 |
|
| 139 |
return activity;
|