Select Commodity and Load Historical Data
Sample data is illustrative and limited.
Data Preview ():
| Date | Price ($) |
|---|
Historical Analysis for Commodity
Load data in the 'Data Input & Setup' tab to perform analysis.
Overall Summary Statistics:
Custom Period Analysis:
Period Summary ():
Data for Selected Period:
| Date | Price ($) |
|---|
No data loaded.
'; document.getElementById('hcdtPeriodSummary').classList.add('hcdt-hidden'); document.getElementById('hcdtPeriodSummary').innerHTML = 'Period Summary:
'; this.clearDataTable('hcdtPeriodDataTableBody', 'hcdtPeriodDataTableContainer', null, ''); const startDateSelect = document.getElementById('hcdtStartDateSelect'); const endDateSelect = document.getElementById('hcdtEndDateSelect'); if(startDateSelect) startDateSelect.innerHTML = ''; if(endDateSelect) endDateSelect.innerHTML = ''; document.getElementById('hcdtAnalysisDataMessage')?.classList.remove('hcdt-hidden'); document.getElementById('hcdtAnalysisContentWrapper')?.classList.add('hcdt-hidden'); }, openTab: function(event, tabId) { if (event) event.preventDefault(); document.querySelectorAll('#hcdtAppContainer .hcdt-tab-content').forEach(tc => tc.classList.remove('active')); document.querySelectorAll('#hcdtAppContainer .hcdt-tab-link').forEach(tl => tl.classList.remove('active')); const selectedTab = document.getElementById(tabId); if(selectedTab) selectedTab.classList.add('active'); else return; let clickedTabLink; if (event && event.currentTarget) clickedTabLink = event.currentTarget; else { document.querySelectorAll('#hcdtAppContainer .hcdt-tab-link').forEach(tl => { if (tl.getAttribute('onclick').includes(tabId)) clickedTabLink = tl; }); } if(clickedTabLink) clickedTabLink.classList.add('active'); this.currentTab = tabId; this.updateNavButtonsVisibility(); if (tabId === 'hcdtAnalysisTab' && this.currentCommodityData.length > 0) { this.setupAnalysisView(); } else if (tabId === 'hcdtAnalysisTab') { this.clearAnalysisDisplay(); // Ensure analysis tab is cleared if no data document.getElementById('hcdtAnalysisDataMessage')?.classList.remove('hcdt-hidden'); document.getElementById('hcdtAnalysisContentWrapper')?.classList.add('hcdt-hidden'); } }, updateNavButtonsVisibility: function() { const prevBtnDataInput = document.querySelector('#hcdtDataInputTab .prev-tab-btn'); const nextBtnDataInput = document.querySelector('#hcdtDataInputTab .next-tab-btn'); const prevBtnAnalysis = document.querySelector('#hcdtAnalysisTab .prev-tab-btn'); const nextBtnAnalysis = document.querySelector('#hcdtAnalysisTab .next-tab-btn'); if (!prevBtnDataInput || !nextBtnDataInput || !prevBtnAnalysis || !nextBtnAnalysis) return; prevBtnDataInput.style.visibility = (this.currentTab === 'hcdtDataInputTab') ? 'hidden' : 'visible'; nextBtnDataInput.style.visibility = (this.currentTab === 'hcdtDataInputTab') ? 'visible' : 'hidden'; prevBtnAnalysis.style.visibility = (this.currentTab === 'hcdtAnalysisTab') ? 'visible' : 'hidden'; nextBtnAnalysis.style.visibility = (this.currentTab === 'hcdtAnalysisTab') ? 'hidden' : 'visible'; }, handleFileUpload: function(event) { const fileInput = event.target; const csvErrorEl = document.getElementById('hcdtCsvError'); if (!fileInput || !csvErrorEl) return; csvErrorEl.classList.add('hcdt-hidden'); csvErrorEl.textContent = ''; if (!fileInput.files || fileInput.files.length === 0) return; const file = fileInput.files[0]; if (file && file.type === "text/csv") { const reader = new FileReader(); reader.onload = (e) => { try { const text = e.target.result; this.currentCommodityData = this.parseCSV(text); this.sortData(); this.populateDataTable(this.currentCommodityData, 'hcdtDataTablePreviewBody', 'hcdtDataTableContainer', 'hcdtDataMessage'); // If data loaded, auto-setup analysis view if user navigates there OR if already there if(this.currentTab === 'hcdtAnalysisTab') this.setupAnalysisView(); } catch (error) { csvErrorEl.textContent = 'Error parsing CSV: ' + error.message + ". Ensure format is Date,Price (e.g., YYYY-MM-DD,100.50 or MM/DD/YYYY,100.50). Header row is optional."; csvErrorEl.classList.remove('hcdt-hidden'); this.currentCommodityData = []; this.clearDataTable('hcdtDataTablePreviewBody', 'hcdtDataTableContainer', 'hcdtDataMessage', 'Failed to load data from CSV.'); this.clearAnalysisDisplay(); } }; reader.onerror = () => { /* ... error handling ... */ }; reader.readAsText(file); } else { csvErrorEl.textContent = 'Please upload a valid .csv file.'; csvErrorEl.classList.remove('hcdt-hidden'); } fileInput.value = ''; // Reset file input }, parseCSV: function(csvText) { const lines = csvText.trim().split(/\r\n|\n/); const data = []; let startLine = 0; if (lines.length > 0) { // Basic header skip: if second col of first line is not a number const firstLineParts = lines[0].split(','); if (firstLineParts.length === 2 && isNaN(parseFloat(firstLineParts[1].trim()))) { startLine = 1; } } for (let i = startLine; i < lines.length; i++) { if (lines[i].trim() === "") continue; const parts = lines[i].split(','); if (parts.length === 2) { const dateStr = parts[0].trim(); const priceStr = parts[1].trim(); const date = new Date(dateStr); // Robust date parsing const price = parseFloat(priceStr); if (!isNaN(date.getTime()) && !isNaN(price)) { data.push({ date, price }); } else { console.warn(`Skipping invalid CSV row: ${lines[i]}`); } } else { console.warn(`Skipping malformed CSV row (expected 2 columns): ${lines[i]}`);} } if (data.length === 0 && lines.length > 0 && (lines.length > startLine)) { throw new Error("No valid data rows found. Ensure CSV has Date,Price columns and numeric prices."); } return data; }, loadSampleData: function() { const sample = this.SAMPLE_HISTORICAL_DATA[this.selectedCommodityName]; if (sample) { this.currentCommodityData = sample.map(item => ({ date: new Date(item.date), // Ensure Date objects price: parseFloat(item.price) })); this.sortData(); this.populateDataTable(this.currentCommodityData, 'hcdtDataTablePreviewBody', 'hcdtDataTableContainer', 'hcdtDataMessage'); if(this.currentTab === 'hcdtAnalysisTab') this.setupAnalysisView(); document.getElementById('hcdtCsvError').classList.add('hcdt-hidden'); } else { this.clearDataTable('hcdtDataTablePreviewBody', 'hcdtDataTableContainer', 'hcdtDataMessage', 'No sample data for this commodity.'); this.clearAnalysisDisplay(); } }, sortData: function() { this.currentCommodityData.sort((a, b) => a.date - b.date); }, populateDataTable: function(dataArray, tbodyIdSelectorPart, containerId, messageId) { const tbody = document.querySelector(`#${containerId} tbody`) || document.getElementById(tbodyIdSelectorPart); // More flexible selector const tableContainer = document.getElementById(containerId); const dataMessageEl = document.getElementById(messageId); if (!tbody || !tableContainer) { console.error("Table elements for populateDataTable not found:", containerId); return; } tbody.innerHTML = ''; if (!dataArray || dataArray.length === 0) { tableContainer.classList.add('hcdt-hidden'); if (dataMessageEl) { dataMessageEl.textContent = "No data to display."; dataMessageEl.classList.remove('hcdt-hidden'); } return; } dataArray.forEach(item => { const row = tbody.insertRow(); row.insertCell().textContent = item.date.toLocaleDateString(); row.insertCell().textContent = `$${item.price.toFixed(2)}`; }); tableContainer.classList.remove('hcdt-hidden'); if (dataMessageEl) dataMessageEl.classList.add('hcdt-hidden'); }, navigateToAnalysisTab: function() { if (this.currentCommodityData.length === 0) { alert("Please load data for the selected commodity before viewing analysis."); return; } this.setupAnalysisView(); // Setup view elements before navigation this.navigateToTab('hcdtAnalysisTab'); }, setupAnalysisView: function() { document.getElementById('hcdtAnalysisCommodityName').textContent = this.selectedCommodityName; if (this.currentCommodityData.length === 0) { this.clearAnalysisDisplay(); document.getElementById('hcdtAnalysisDataMessage')?.classList.remove('hcdt-hidden'); document.getElementById('hcdtAnalysisContentWrapper')?.classList.add('hcdt-hidden'); return; } document.getElementById('hcdtAnalysisDataMessage')?.classList.add('hcdt-hidden'); document.getElementById('hcdtAnalysisContentWrapper')?.classList.remove('hcdt-hidden'); this.renderChart(); this.displayOverallSummary(); this.populatePeriodDateSelectors(); // Clear previous period analysis results document.getElementById('hcdtPeriodSummary').classList.add('hcdt-hidden'); document.getElementById('hcdtPeriodSummary').innerHTML = 'Period Summary:
'; this.clearDataTable('hcdtPeriodDataTableBody', 'hcdtPeriodDataTableContainer', null, ''); }, renderChart: function() { if (typeof Chart === 'undefined' || typeof Chart !== 'function') { console.error("Chart.js missing in renderChart"); return; } const ctx = document.getElementById('historicalCommodityChart')?.getContext('2d'); if (!ctx) { console.error("Chart context not found"); return; } if (this.chartInstance) this.chartInstance.destroy(); this.chartInstance = new Chart(ctx, { type: 'line', data: { labels: this.currentCommodityData.map(item => item.date), // Chart.js time scale handles formatting datasets: [{ label: `${this.selectedCommodityName} Price (USD $)`, data: this.currentCommodityData.map(item => item.price), borderColor: '#6f42c1', backgroundColor: 'rgba(111, 66, 193, 0.1)', fill: true, tension: 0.1 }] }, options: { responsive: true, maintainAspectRatio: true, scales: { x: { type: 'time', time: { unit: 'month' }, title: { display: true, text: 'Date' } }, y: { title: { display: true, text: 'Price (USD $)' }, ticks: { callback: value => '$' + parseFloat(value).toFixed(2) } } }, plugins: { tooltip: { callbacks: { label: ctx => `${ctx.dataset.label}: $${parseFloat(ctx.parsed.y).toFixed(2)}` } } } } }); }, displayOverallSummary: function() { const summaryEl = document.getElementById('hcdtOverallSummary'); if (!summaryEl || this.currentCommodityData.length === 0) { if(summaryEl) summaryEl.innerHTML = 'Overall Summary Statistics:
No data available.
'; return; } const data = this.currentCommodityData; // Already sorted const firstDP = data[0]; const lastDP = data[data.length - 1]; let high = {price: -Infinity, date: ''}; let low = {price: Infinity, date: ''}; let sum = 0; data.forEach(item => { if(item.price > high.price) { high.price = item.price; high.date = item.date.toLocaleDateString(); } if(item.price < low.price) { low.price = item.price; low.date = item.date.toLocaleDateString(); } sum += item.price; }); const avg = sum / data.length; summaryEl.innerHTML = `Overall Summary Statistics:
Date Range: ${firstDP.date.toLocaleDateString()} - ${lastDP.date.toLocaleDateString()}
Data Points: ${data.length}
Highest Price: $${high.price.toFixed(2)} (on ${high.date})
Lowest Price: $${low.price.toFixed(2)} (on ${low.date})
Average Price: $${avg.toFixed(2)}
`; }, populatePeriodDateSelectors: function() { const startDateSelect = document.getElementById('hcdtStartDateSelect'); const endDateSelect = document.getElementById('hcdtEndDateSelect'); if (!startDateSelect || !endDateSelect || this.currentCommodityData.length === 0) return; startDateSelect.innerHTML = ''; endDateSelect.innerHTML = ''; // Using a Set to get unique dates, then sorting them const uniqueDates = [...new Set(this.currentCommodityData.map(item => item.date.getTime()))] .map(time => new Date(time)) .sort((a,b) => a - b); uniqueDates.forEach(date => { const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD for value const displayDate = date.toLocaleDateString(); const startOption = document.createElement('option'); startOption.value = dateStr; startOption.textContent = displayDate; startDateSelect.appendChild(startOption); const endOption = document.createElement('option'); endOption.value = dateStr; endOption.textContent = displayDate; endDateSelect.appendChild(endOption); }); }, analyzeCustomPeriod: function() { const startDateStr = document.getElementById('hcdtStartDateSelect').value; const endDateStr = document.getElementById('hcdtEndDateSelect').value; const periodSummaryEl = document.getElementById('hcdtPeriodSummary'); const periodRangeDisplayEl = document.getElementById('hcdtPeriodRangeDisplay'); if (!startDateStr || !endDateStr || !periodSummaryEl) { alert("Please select both a start and end date for period analysis."); return; } const startDate = new Date(startDateStr + "T00:00:00"); // Ensure start of day const endDate = new Date(endDateStr + "T23:59:59"); // Ensure end of day if (startDate > endDate) { alert("Start date cannot be after end date."); periodSummaryEl.innerHTML = 'Period Summary:
Error: Start date cannot be after end date.
'; periodSummaryEl.classList.remove('hcdt-hidden'); this.clearDataTable('hcdtPeriodDataTableBody', 'hcdtPeriodDataTableContainer', null, ''); return; } const periodData = this.currentCommodityData.filter(item => item.date >= startDate && item.date <= endDate); if (periodData.length === 0) { periodSummaryEl.innerHTML = `Period Summary (${new Date(startDateStr).toLocaleDateString()} - ${new Date(endDateStr).toLocaleDateString()}):
No data available for the selected period.
`; periodSummaryEl.classList.remove('hcdt-hidden'); this.clearDataTable('hcdtPeriodDataTableBody', 'hcdtPeriodDataTableContainer', null, ''); return; } // Sort periodData just in case, though currentCommodityData should be sorted periodData.sort((a,b) => a.date - b.date); const firstDP = periodData[0]; const lastDP = periodData[periodData.length - 1]; let high = {price: -Infinity, date: ''}; let low = {price: Infinity, date: ''}; let sum = 0; periodData.forEach(item => { if(item.price > high.price) { high.price = item.price; high.date = item.date.toLocaleDateString(); } if(item.price < low.price) { low.price = item.price; low.date = item.date.toLocaleDateString(); } sum += item.price; }); const avg = sum / periodData.length; const netChange = lastDP.price - firstDP.price; const pctChange = (firstDP.price === 0) ? (netChange === 0 ? 0 : Infinity) : (netChange / firstDP.price) * 100; periodSummaryEl.innerHTML = `Period Summary (${firstDP.date.toLocaleDateString()} - ${lastDP.date.toLocaleDateString()}):
Data Points: ${periodData.length}
Highest Price: $${high.price.toFixed(2)} (on ${high.date})
Lowest Price: $${low.price.toFixed(2)} (on ${low.date})
Average Price: $${avg.toFixed(2)}
Net Change: ${netChange >= 0 ? '+' : ''}$${netChange.toFixed(2)}
Percentage Change: ${pctChange.toFixed(2)}%
`; periodSummaryEl.classList.remove('hcdt-hidden'); this.populateDataTable(periodData, 'hcdtPeriodDataTableBody', 'hcdtPeriodDataTableContainer', null); }, downloadResultsAsPDF: function() { // Ensure analysis tab content is up-to-date for PDF if(this.currentTab !== 'hcdtAnalysisTab' && this.currentCommodityData.length > 0) { this.setupAnalysisView(); // Make sure analysis view is rendered } else if (this.currentCommodityData.length === 0) { alert("No data to download. Please load data first."); return; } const analysisTab = document.getElementById('hcdtAnalysisTab'); const dataInputTab = document.getElementById('hcdtDataInputTab'); let originalDataInputDisplay = ''; if(dataInputTab) { originalDataInputDisplay = dataInputTab.style.display; dataInputTab.style.display = 'none'; } if(analysisTab) analysisTab.style.display = 'block'; setTimeout(() => { window.print(); // Restore display if(dataInputTab) dataInputTab.style.display = originalDataInputDisplay; if(analysisTab && this.currentTab !== 'hcdtAnalysisTab') analysisTab.style.display = 'none'; else if (analysisTab && this.currentTab === 'hcdtAnalysisTab') analysisTab.style.display = 'block'; }, 750); } }; HCDT_App.init();