diff --git a/src/baby_monitor/static/sleep.html b/src/baby_monitor/static/sleep.html index 6f2f57b..7839437 100644 --- a/src/baby_monitor/static/sleep.html +++ b/src/baby_monitor/static/sleep.html @@ -480,7 +480,7 @@
-

Sleep Sessions per Day

+

Total Sleep Time

@@ -539,7 +539,11 @@ function prepareBarChartData(sleepSessions, timeRange) { const now = new Date(); const labels = []; - const data = []; + const durations = []; // Array of arrays, each containing individual session durations + const sessionCounts = []; // For reference + + // Only use completed sleep sessions + const completedSessions = sleepSessions.filter(s => s.end_time); // If 24 hours selected, show 3-hour aggregates if (timeRange === 1) { @@ -561,13 +565,18 @@ }); labels.push(`${startLabel}-${endLabel}`); - // Count sleep sessions for this 3-hour period - const count = sleepSessions.filter((session) => { + // Get individual session durations for this 3-hour period (in hours) + const periodSessions = completedSessions.filter((session) => { const startDate = new Date(session.start_time); return startDate >= periodStart && startDate < periodEnd; - }).length; - - data.push(count); + }); + + const sessionDurations = periodSessions.map(session => + calculateDuration(session.start_time, session.end_time) / 60 + ); + + durations.push(sessionDurations); + sessionCounts.push(periodSessions.length); } } else if (timeRange === 3) { // Show 8-hour aggregates for 3 days (9 buckets total) @@ -594,13 +603,18 @@ } labels.push(periodLabel); - // Count sleep sessions for this 8-hour period - const count = sleepSessions.filter((session) => { + // Get individual session durations for this 8-hour period (in hours) + const periodSessions = completedSessions.filter((session) => { const startDate = new Date(session.start_time); return startDate >= periodStart && startDate < periodEnd; - }).length; - - data.push(count); + }); + + const sessionDurations = periodSessions.map(session => + calculateDuration(session.start_time, session.end_time) / 60 + ); + + durations.push(sessionDurations); + sessionCounts.push(periodSessions.length); } } else { // Create days based on timeRange (including today) @@ -615,20 +629,38 @@ }); labels.push(dateStr); - // Count sleep sessions for this day + // Get individual session durations for this day (in hours) const nextDay = new Date(date); nextDay.setDate(nextDay.getDate() + 1); - const count = sleepSessions.filter((session) => { + const daySessions = completedSessions.filter((session) => { const startDate = new Date(session.start_time); return startDate >= date && startDate < nextDay; - }).length; - - data.push(count); + }); + + const sessionDurations = daySessions.map(session => { + const dur = calculateDuration(session.start_time, session.end_time) / 60; + console.log(` Session: ${session.start_time} to ${session.end_time}, duration: ${dur} hours (${calculateDuration(session.start_time, session.end_time)} minutes)`); + return dur; + }); + + console.log(`${dateStr}: ${daySessions.length} sessions, durations:`, sessionDurations); + + durations.push(sessionDurations); + sessionCounts.push(daySessions.length); } } - return { labels, data }; + console.log('Final prepareBarChartData result:', { labels, durations, sessionCounts }); + + // Log the actual duration values for debugging + durations.forEach((periodDurations, idx) => { + if (periodDurations.length > 0) { + console.log(` ${labels[idx]}: durations =`, periodDurations); + } + }); + + return { labels, durations, sessionCounts }; } function prepareDurationDistributionData(sleeps) { @@ -657,30 +689,119 @@ }; } + let barChartInstance = null; + let durationChartInstance = null; + let avgDurationChartInstance = null; + function renderBarChart(chartData) { const ctx = document.getElementById("barChart"); - new Chart(ctx, { + + // Destroy existing chart if it exists + if (barChartInstance) { + barChartInstance.destroy(); + } + + // Debug: Log the data + console.log('Bar chart data:', chartData); + + // Find the maximum number of sessions in any period + const maxSessions = Math.max(...chartData.durations.map(d => d.length), 0); + + console.log('Max sessions:', maxSessions); + + // If no sessions at all, show empty chart + if (maxSessions === 0) { + barChartInstance = new Chart(ctx, { + type: "bar", + data: { + labels: chartData.labels, + datasets: [{ + label: "No Data", + data: new Array(chartData.labels.length).fill(0), + backgroundColor: "rgba(79, 172, 254, 0.3)", + }], + }, + options: { + responsive: true, + maintainAspectRatio: false, + scales: { + y: { + beginAtZero: true, + max: 10, + title: { + display: true, + text: "Hours", + }, + }, + }, + plugins: { + legend: { + display: false, + }, + }, + }, + }); + return; + } + + // Create color palette for different sessions + const colors = [ + 'rgba(79, 172, 254, 0.8)', + 'rgba(0, 242, 254, 0.8)', + 'rgba(58, 155, 232, 0.8)', + 'rgba(32, 137, 220, 0.8)', + 'rgba(21, 119, 208, 0.8)', + 'rgba(11, 101, 196, 0.8)', + 'rgba(79, 172, 254, 0.6)', + 'rgba(0, 242, 254, 0.6)', + 'rgba(58, 155, 232, 0.6)', + 'rgba(32, 137, 220, 0.6)', + ]; + + // Create datasets - one for each session position + const datasets = []; + for (let sessionIndex = 0; sessionIndex < maxSessions; sessionIndex++) { + const dataForSession = chartData.durations.map(periodDurations => + periodDurations[sessionIndex] || 0 + ); + + console.log(`Session ${sessionIndex + 1} data:`, dataForSession); + + datasets.push({ + label: `Session ${sessionIndex + 1}`, + data: dataForSession, + backgroundColor: colors[sessionIndex % colors.length], + borderColor: colors[sessionIndex % colors.length].replace('0.8', '1').replace('0.6', '1'), + borderWidth: 1, + }); + } + + console.log('Datasets:', datasets); + + barChartInstance = new Chart(ctx, { type: "bar", data: { labels: chartData.labels, - datasets: [ - { - label: "Sleep Sessions", - data: chartData.data, - backgroundColor: "rgba(79, 172, 254, 0.6)", - borderColor: "rgba(79, 172, 254, 1)", - borderWidth: 1, - }, - ], + datasets: datasets, }, options: { responsive: true, maintainAspectRatio: false, scales: { + x: { + stacked: true, + }, y: { + stacked: true, beginAtZero: true, + title: { + display: true, + text: "Hours", + }, ticks: { - stepSize: 1, + callback: function(value) { + return value.toFixed(1) + 'h'; + } }, }, }, @@ -688,6 +809,23 @@ legend: { display: false, }, + tooltip: { + callbacks: { + label: function(context) { + const hours = Math.floor(context.parsed.y); + const minutes = Math.round((context.parsed.y - hours) * 60); + return `${context.dataset.label}: ${hours}h ${minutes}m`; + }, + footer: function(tooltipItems) { + const periodIndex = tooltipItems[0].dataIndex; + const totalHours = chartData.durations[periodIndex].reduce((sum, val) => sum + val, 0); + const hours = Math.floor(totalHours); + const minutes = Math.round((totalHours - hours) * 60); + const sessionCount = chartData.sessionCounts[periodIndex]; + return `Total: ${hours}h ${minutes}m (${sessionCount} session${sessionCount !== 1 ? 's' : ''})`; + } + } + }, }, }, }); @@ -695,7 +833,13 @@ function renderDurationChart(chartData) { const ctx = document.getElementById("durationChart"); - new Chart(ctx, { + + // Destroy existing chart if it exists + if (durationChartInstance) { + durationChartInstance.destroy(); + } + + durationChartInstance = new Chart(ctx, { type: "pie", data: { labels: chartData.labels, @@ -726,6 +870,11 @@ } function renderAvgDurationChart(sleeps, timeRange) { + // Destroy existing chart if it exists + if (avgDurationChartInstance) { + avgDurationChartInstance.destroy(); + } + const days = {}; const today = new Date(); today.setHours(0, 0, 0, 0); @@ -768,7 +917,7 @@ ); const ctx = document.getElementById("avgDurationChart"); - new Chart(ctx, { + avgDurationChartInstance = new Chart(ctx, { type: "line", data: { labels,