WeightTracker/js/charts.js

182 lines
6.0 KiB
JavaScript

/**
* 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 = '<div class="placeholder-message">No weight data available for the selected period</div>';
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 = '<canvas id="weight-chart-canvas"></canvas>';
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 = `<div class="placeholder-message error">${message}</div>`;
};
// Return public API
return {
init,
renderWeightChart,
updateDateFilter
};
})();