/** * Charts Module * Handles data visualization and charts */ const Charts = (() => { // Chart configuration let weightChartInstance = null; let dateFilter = { startDate: null, endDate: null }; /** * Initialize charts */ const init = () => { // Load Chart.js from CDN if not available if (typeof Chart === 'undefined') { loadChartJS().then(() => { renderWeightChart(); }).catch(error => { console.error('Failed to load Chart.js:', error); showChartError('Failed to load chart library'); }); } else { renderWeightChart(); } }; /** * Load Chart.js and required adapters from CDN * @returns {Promise} - Resolves when Chart.js is loaded */ const loadChartJS = () => { return new Promise((resolve, reject) => { // First load Chart.js core const chartScript = document.createElement('script'); chartScript.src = 'https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js'; chartScript.crossOrigin = 'anonymous'; chartScript.onload = () => { // Then load the date adapter const adapterScript = document.createElement('script'); adapterScript.src = 'https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns@2.0.0/dist/chartjs-adapter-date-fns.bundle.min.js'; adapterScript.crossOrigin = 'anonymous'; adapterScript.onload = () => resolve(); adapterScript.onerror = () => reject(new Error('Failed to load Chart.js date adapter')); document.head.appendChild(adapterScript); }; chartScript.onerror = () => reject(new Error('Failed to load Chart.js')); document.head.appendChild(chartScript); }); }; /** * Render weight chart */ const renderWeightChart = () => { if (typeof Chart === 'undefined') { return; } const chartContainer = document.getElementById('weight-chart'); // Get weight data with filters applied const weights = DataManager.getWeights(dateFilter); if (weights.length === 0) { chartContainer.innerHTML = '
'; return; } // Sort by date (oldest first for chart) const sortedWeights = [...weights].sort((a, b) => new Date(a.date) - new Date(b.date)); // Prepare data for the chart const labels = sortedWeights.map(entry => entry.date); const data = sortedWeights.map(entry => entry.weight); // Clear existing chart if (weightChartInstance) { weightChartInstance.destroy(); } // Create canvas element chartContainer.innerHTML = ''; const ctx = document.getElementById('weight-chart-canvas').getContext('2d'); // Create new chart weightChartInstance = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: 'Weight (kg)', data: data, fill: false, borderColor: '#4a6fa5', tension: 0.1, pointBackgroundColor: '#4a6fa5', pointRadius: 5, pointHoverRadius: 7 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { type: 'time', time: { unit: 'day', displayFormats: { day: 'MMM d' } }, title: { display: true, text: 'Date' } }, y: { title: { display: true, text: 'Weight (kg)' }, beginAtZero: false } }, plugins: { legend: { display: true, position: 'top' }, tooltip: { callbacks: { title: function(tooltipItems) { const date = new Date(tooltipItems[0].label); return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); } } } } } }); }; /** * Update the date filter and refresh the chart * @param {string} startDate - Start date in YYYY-MM-DD format * @param {string} endDate - End date in YYYY-MM-DD format */ const updateDateFilter = (startDate, endDate) => { dateFilter.startDate = startDate || null; dateFilter.endDate = endDate || null; renderWeightChart(); }; /** * Show error message in chart container * @param {string} message - Error message */ const showChartError = (message) => { const chartContainer = document.getElementById('weight-chart'); chartContainer.innerHTML = ``; }; // Return public API return { init, renderWeightChart, updateDateFilter }; })();