Добавлен график веса с целевой линией (58 кг за 8 недель)
This commit is contained in:
parent
14fb2b7e81
commit
c15d07a9ad
133
index.html
133
index.html
@ -490,6 +490,19 @@
|
|||||||
<button class="btn" onclick="addProgress()">Записать</button>
|
<button class="btn" onclick="addProgress()">Записать</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress-stats" id="progress-stats"></div>
|
<div class="progress-stats" id="progress-stats"></div>
|
||||||
|
|
||||||
|
<!-- График веса -->
|
||||||
|
<div style="margin-top: 30px; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: 15px; padding: 25px;">
|
||||||
|
<h3>📈 График веса</h3>
|
||||||
|
<p style="margin-bottom: 15px; color: #666;">Цель: <strong>58 кг</strong> через 8 недель</p>
|
||||||
|
<canvas id="weight-chart" height="80" style="width: 100%; background: white; border-radius: 10px;"></canvas>
|
||||||
|
<div style="display: flex; justify-content: space-between; margin-top: 10px; font-size: 12px; color: #666;">
|
||||||
|
<span>Неделя 1</span>
|
||||||
|
<span>Неделя 4</span>
|
||||||
|
<span>Неделя 8</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="progress-history" style="margin-top: 30px;"></div>
|
<div id="progress-history" style="margin-top: 30px;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1018,6 +1031,126 @@
|
|||||||
).join('') : '<p>Пока нет записей. Заполни данные выше!</p>';
|
).join('') : '<p>Пока нет записей. Заполни данные выше!</p>';
|
||||||
|
|
||||||
renderNutrition();
|
renderNutrition();
|
||||||
|
renderWeightChart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderWeightChart() {
|
||||||
|
const canvas = document.getElementById('weight-chart');
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
canvas.width = rect.width;
|
||||||
|
canvas.height = rect.height;
|
||||||
|
|
||||||
|
const entries = progressData.entries.sort((a, b) => new Date(a.date) - new Date(b.date));
|
||||||
|
const startDate = new Date('2026-08-10');
|
||||||
|
const totalWeeks = 8;
|
||||||
|
const startWeight = 65;
|
||||||
|
const targetWeight = 58;
|
||||||
|
|
||||||
|
// Данные для графика
|
||||||
|
const weights = [];
|
||||||
|
const weeks = [];
|
||||||
|
|
||||||
|
for (let w = 0; w <= totalWeeks; w++) {
|
||||||
|
weeks.push(w);
|
||||||
|
// Целевая линия: 65 - (w * 7/8)
|
||||||
|
weights.push({
|
||||||
|
week: w,
|
||||||
|
target: startWeight - (w * (startWeight - targetWeight) / totalWeeks),
|
||||||
|
actual: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем фактические замеры
|
||||||
|
entries.forEach(e => {
|
||||||
|
const date = new Date(e.date);
|
||||||
|
const daysFromStart = (date - startDate) / (1000 * 60 * 60 * 24);
|
||||||
|
const week = Math.min(8, Math.max(0, daysFromStart / 7));
|
||||||
|
|
||||||
|
if (e.weight) {
|
||||||
|
const weekIndex = Math.round(week);
|
||||||
|
if (weekIndex >= 0 && weekIndex <= 8) {
|
||||||
|
weights[weekIndex].actual = parseFloat(e.weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Очистка
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// Настройки
|
||||||
|
const padding = 40;
|
||||||
|
const chartWidth = canvas.width - padding * 2;
|
||||||
|
const chartHeight = canvas.height - padding * 2;
|
||||||
|
const weightRange = 10; // 55-75 кг
|
||||||
|
const minWeight = 55;
|
||||||
|
|
||||||
|
function x(w) { return padding + (w / totalWeeks) * chartWidth; }
|
||||||
|
function y(weight) { return padding + chartHeight - ((weight - minWeight) / weightRange) * chartHeight; }
|
||||||
|
|
||||||
|
// Сетка
|
||||||
|
ctx.strokeStyle = '#e0e0e5';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (let w = 0; w <= totalWeeks; w += 2) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x(w), padding);
|
||||||
|
ctx.lineTo(x(w), canvas.height - padding);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
for (let wt = 56; wt <= 74; wt += 2) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding, y(wt));
|
||||||
|
ctx.lineTo(canvas.width - padding, y(wt));
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Целевая линия (красная пунктирная)
|
||||||
|
ctx.strokeStyle = '#f5576c';
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.setLineDash([10, 5]);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x(0), y(startWeight));
|
||||||
|
ctx.lineTo(x(8), y(targetWeight));
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
|
||||||
|
// Фактические замеры (синяя линия)
|
||||||
|
ctx.strokeStyle = '#667eea';
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.beginPath();
|
||||||
|
let started = false;
|
||||||
|
for (let w = 0; w <= 8; w++) {
|
||||||
|
if (weights[w].actual !== null) {
|
||||||
|
if (!started) {
|
||||||
|
ctx.moveTo(x(w), y(weights[w].actual));
|
||||||
|
started = true;
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x(w), y(weights[w].actual));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Точки замеров
|
||||||
|
for (let w = 0; w <= 8; w++) {
|
||||||
|
if (weights[w].actual !== null) {
|
||||||
|
ctx.fillStyle = '#667eea';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x(w), y(weights[w].actual), 6, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Подписи
|
||||||
|
ctx.fillStyle = '#667eea';
|
||||||
|
ctx.font = 'bold 14px sans-serif';
|
||||||
|
ctx.fillText('Цель: 58 кг', canvas.width - padding - 100, y(targetWeight) - 10);
|
||||||
|
|
||||||
|
ctx.fillStyle = '#667eea';
|
||||||
|
ctx.font = 'bold 14px sans-serif';
|
||||||
|
ctx.fillText('Старт: 65 кг', padding, y(startWeight) - 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// === ПИТАНИЕ ===
|
// === ПИТАНИЕ ===
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user