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,