made stacked barchart to show individual sleep session lengths

This commit is contained in:
Brian Bjarke Jensen
2025-11-11 14:19:41 +01:00
parent 954e5a9f6f
commit b1fae46f28
+177 -28
View File
@@ -480,7 +480,7 @@
</div> </div>
<div class="chart-container"> <div class="chart-container">
<h2>Sleep Sessions per Day</h2> <h2>Total Sleep Time</h2>
<div class="chart-wrapper"> <div class="chart-wrapper">
<canvas id="barChart"></canvas> <canvas id="barChart"></canvas>
</div> </div>
@@ -539,7 +539,11 @@
function prepareBarChartData(sleepSessions, timeRange) { function prepareBarChartData(sleepSessions, timeRange) {
const now = new Date(); const now = new Date();
const labels = []; 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 24 hours selected, show 3-hour aggregates
if (timeRange === 1) { if (timeRange === 1) {
@@ -561,13 +565,18 @@
}); });
labels.push(`${startLabel}-${endLabel}`); labels.push(`${startLabel}-${endLabel}`);
// Count sleep sessions for this 3-hour period // Get individual session durations for this 3-hour period (in hours)
const count = sleepSessions.filter((session) => { const periodSessions = completedSessions.filter((session) => {
const startDate = new Date(session.start_time); const startDate = new Date(session.start_time);
return startDate >= periodStart && startDate < periodEnd; 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) { } else if (timeRange === 3) {
// Show 8-hour aggregates for 3 days (9 buckets total) // Show 8-hour aggregates for 3 days (9 buckets total)
@@ -594,13 +603,18 @@
} }
labels.push(periodLabel); labels.push(periodLabel);
// Count sleep sessions for this 8-hour period // Get individual session durations for this 8-hour period (in hours)
const count = sleepSessions.filter((session) => { const periodSessions = completedSessions.filter((session) => {
const startDate = new Date(session.start_time); const startDate = new Date(session.start_time);
return startDate >= periodStart && startDate < periodEnd; 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 { } else {
// Create days based on timeRange (including today) // Create days based on timeRange (including today)
@@ -615,20 +629,38 @@
}); });
labels.push(dateStr); labels.push(dateStr);
// Count sleep sessions for this day // Get individual session durations for this day (in hours)
const nextDay = new Date(date); const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1); nextDay.setDate(nextDay.getDate() + 1);
const count = sleepSessions.filter((session) => { const daySessions = completedSessions.filter((session) => {
const startDate = new Date(session.start_time); const startDate = new Date(session.start_time);
return startDate >= date && startDate < nextDay; 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) { function prepareDurationDistributionData(sleeps) {
@@ -657,30 +689,119 @@
}; };
} }
let barChartInstance = null;
let durationChartInstance = null;
let avgDurationChartInstance = null;
function renderBarChart(chartData) { function renderBarChart(chartData) {
const ctx = document.getElementById("barChart"); 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", type: "bar",
data: { data: {
labels: chartData.labels, labels: chartData.labels,
datasets: [ datasets: datasets,
{
label: "Sleep Sessions",
data: chartData.data,
backgroundColor: "rgba(79, 172, 254, 0.6)",
borderColor: "rgba(79, 172, 254, 1)",
borderWidth: 1,
},
],
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
scales: { scales: {
x: {
stacked: true,
},
y: { y: {
stacked: true,
beginAtZero: true, beginAtZero: true,
title: {
display: true,
text: "Hours",
},
ticks: { ticks: {
stepSize: 1, callback: function(value) {
return value.toFixed(1) + 'h';
}
}, },
}, },
}, },
@@ -688,6 +809,23 @@
legend: { legend: {
display: false, 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) { function renderDurationChart(chartData) {
const ctx = document.getElementById("durationChart"); const ctx = document.getElementById("durationChart");
new Chart(ctx, {
// Destroy existing chart if it exists
if (durationChartInstance) {
durationChartInstance.destroy();
}
durationChartInstance = new Chart(ctx, {
type: "pie", type: "pie",
data: { data: {
labels: chartData.labels, labels: chartData.labels,
@@ -726,6 +870,11 @@
} }
function renderAvgDurationChart(sleeps, timeRange) { function renderAvgDurationChart(sleeps, timeRange) {
// Destroy existing chart if it exists
if (avgDurationChartInstance) {
avgDurationChartInstance.destroy();
}
const days = {}; const days = {};
const today = new Date(); const today = new Date();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
@@ -768,7 +917,7 @@
); );
const ctx = document.getElementById("avgDurationChart"); const ctx = document.getElementById("avgDurationChart");
new Chart(ctx, { avgDurationChartInstance = new Chart(ctx, {
type: "line", type: "line",
data: { data: {
labels, labels,