diff --git a/index.html b/index.html index 69649ae..6d94de6 100644 --- a/index.html +++ b/index.html @@ -3,14 +3,14 @@ - ETF Trade Tracker + Personal Finance Tracker
- +
diff --git a/script.js b/script.js index 5214495..0349d21 100644 --- a/script.js +++ b/script.js @@ -1,4 +1,4 @@ -class ETFTradeTracker { +class PersonalFinanceTracker { constructor() { this.trades = []; this.currentPrices = new Map(); // Store current market prices @@ -793,6 +793,10 @@ class ETFTradeTracker { // Load and display top accounts for each segment this.loadDashboardTopAccounts(); + + // Initialize and update growth charts + this.initializeGrowthCharts(); + this.updateGrowthCharts(); } updateDashboardColors(totalGains) { @@ -3702,6 +3706,191 @@ class ETFTradeTracker { subscriptionsValue.textContent = '€0.00'; } } + + // Growth Charts Functionality + initializeGrowthCharts() { + // Initialize chart data storage + this.chartData = { + portfolio: [], + cash: [], + total: [], + currentPeriod: '7d' + }; + + // Bind period selector events + const periodButtons = document.querySelectorAll('.period-btn'); + periodButtons.forEach(btn => { + btn.addEventListener('click', (e) => { + // Update active button + periodButtons.forEach(b => b.classList.remove('active')); + e.target.classList.add('active'); + + // Update period and refresh charts + this.chartData.currentPeriod = e.target.dataset.period; + this.updateGrowthCharts(); + }); + }); + + // Generate initial mock data + this.generateMockChartData(); + } + + generateMockChartData() { + // Generate mock historical data for different time periods + const periods = { + '7d': 7, + '1m': 30, + '3m': 90, + '6m': 180, + '1y': 365 + }; + + Object.keys(periods).forEach(period => { + const days = periods[period]; + const portfolioData = []; + const cashData = []; + const totalData = []; + + // Get current values as base + const currentPortfolio = this.calculatePortfolioTotals().currentValue; + const currentCash = 5000; // Mock current cash value + const currentTotal = currentPortfolio + currentCash; + + // Generate historical values with some growth trend + for (let i = days; i >= 0; i--) { + const date = new Date(); + date.setDate(date.getDate() - i); + + // Add some randomness but overall upward trend + const growthFactor = Math.pow(1.0008, days - i); // 0.08% daily growth on average + const randomFactor = 0.95 + Math.random() * 0.1; // ±5% random variation + + const portfolioValue = currentPortfolio / growthFactor * randomFactor; + const cashValue = currentCash + (Math.random() - 0.5) * 1000; // Cash fluctuates more + + portfolioData.push({ date, value: portfolioValue }); + cashData.push({ date, value: Math.max(0, cashValue) }); + totalData.push({ date, value: portfolioValue + Math.max(0, cashValue) }); + } + + this.chartData[`portfolio_${period}`] = portfolioData; + this.chartData[`cash_${period}`] = cashData; + this.chartData[`total_${period}`] = totalData; + }); + } + + updateGrowthCharts() { + const period = this.chartData.currentPeriod; + + // Update portfolio chart + this.renderChart('portfolio-chart', this.chartData[`portfolio_${period}`], '#28a745'); + this.updateChartStats('portfolio', this.chartData[`portfolio_${period}`]); + + // Update cash chart + this.renderChart('cash-chart', this.chartData[`cash_${period}`], '#17a2b8'); + this.updateChartStats('cash', this.chartData[`cash_${period}`]); + + // Update total chart + this.renderChart('total-chart', this.chartData[`total_${period}`], '#667eea'); + this.updateChartStats('total', this.chartData[`total_${period}`]); + } + + renderChart(canvasId, data, color) { + const canvas = document.getElementById(canvasId); + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + const rect = canvas.getBoundingClientRect(); + + // Set canvas size + canvas.width = rect.width * window.devicePixelRatio; + canvas.height = rect.height * window.devicePixelRatio; + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + + // Clear canvas + ctx.clearRect(0, 0, rect.width, rect.height); + + if (!data || data.length < 2) return; + + // Calculate bounds + const values = data.map(d => d.value); + const minValue = Math.min(...values); + const maxValue = Math.max(...values); + const valueRange = maxValue - minValue; + const padding = 20; + + // Draw grid lines + ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--border-light'); + ctx.lineWidth = 1; + ctx.setLineDash([2, 2]); + + for (let i = 0; i <= 4; i++) { + const y = padding + (i * (rect.height - 2 * padding)) / 4; + ctx.beginPath(); + ctx.moveTo(padding, y); + ctx.lineTo(rect.width - padding, y); + ctx.stroke(); + } + ctx.setLineDash([]); + + // Draw line chart + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.beginPath(); + + data.forEach((point, index) => { + const x = padding + (index * (rect.width - 2 * padding)) / (data.length - 1); + const y = rect.height - padding - ((point.value - minValue) / valueRange) * (rect.height - 2 * padding); + + if (index === 0) { + ctx.moveTo(x, y); + } else { + ctx.lineTo(x, y); + } + }); + + ctx.stroke(); + + // Fill area under curve + ctx.globalAlpha = 0.1; + ctx.fillStyle = color; + ctx.lineTo(rect.width - padding, rect.height - padding); + ctx.lineTo(padding, rect.height - padding); + ctx.closePath(); + ctx.fill(); + ctx.globalAlpha = 1; + } + + updateChartStats(type, data) { + if (!data || data.length < 2) return; + + const latest = data[data.length - 1]; + const previous = data[0]; + const change = ((latest.value - previous.value) / previous.value) * 100; + + // Update value display + const valueElement = document.getElementById(`${type}-graph-value`); + const changeElement = document.getElementById(`${type}-graph-change`); + + if (valueElement) { + valueElement.textContent = this.formatCurrency(latest.value, 'EUR'); + } + + if (changeElement) { + const changeText = `${change >= 0 ? '+' : ''}${change.toFixed(1)}%`; + changeElement.textContent = changeText; + + // Update change indicator classes + changeElement.className = 'change-indicator'; + if (change > 0) { + changeElement.classList.add('positive'); + } else if (change < 0) { + changeElement.classList.add('negative'); + } else { + changeElement.classList.add('neutral'); + } + } + } } -const app = new ETFTradeTracker(); \ No newline at end of file +const app = new PersonalFinanceTracker(); \ No newline at end of file diff --git a/styles.css b/styles.css index 263de84..04ff9bf 100644 --- a/styles.css +++ b/styles.css @@ -942,6 +942,258 @@ body { border-left-color: var(--info); } +/* Growth Graphs Section */ +.growth-graphs-section { + margin-top: 40px; + max-width: 1200px; + margin-left: auto; + margin-right: auto; +} + +.graphs-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 25px; +} + +.graphs-header h2 { + margin: 0; + color: var(--text-primary); + font-size: 1.5rem; + font-weight: 600; +} + +.time-period-selector { + display: flex; + gap: 8px; + background: var(--bg-secondary); + padding: 4px; + border-radius: 8px; + box-shadow: var(--shadow-light); +} + +.period-btn { + background: transparent; + border: none; + padding: 8px 16px; + border-radius: 6px; + font-size: 0.85rem; + font-weight: 600; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.2s ease; +} + +.period-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.period-btn.active { + background: var(--accent-primary); + color: white; + box-shadow: 0 2px 4px rgba(102, 126, 234, 0.2); +} + +.graphs-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 20px; +} + +.graph-card { + background: var(--bg-secondary); + border-radius: 12px; + padding: 20px; + box-shadow: var(--shadow-light); + border-left: 4px solid var(--accent-primary); + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.graph-card:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-medium); +} + +.graph-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.graph-header h3 { + margin: 0; + font-size: 1rem; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; +} + +.graph-stats { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 2px; +} + +.current-value { + font-size: 1.2rem; + font-weight: 700; + color: var(--text-primary); +} + +.change-indicator { + font-size: 0.8rem; + font-weight: 600; + padding: 2px 6px; + border-radius: 4px; +} + +.change-indicator.positive { + color: var(--success); + background: var(--success-bg); +} + +.change-indicator.negative { + color: var(--danger); + background: var(--danger-bg); +} + +.change-indicator.neutral { + color: var(--text-muted); + background: var(--bg-tertiary); +} + +.graph-container { + height: 200px; + position: relative; + background: var(--bg-primary); + border-radius: 8px; + padding: 15px; + overflow: hidden; +} + +.chart-canvas { + width: 100%; + height: 100%; + display: block; +} + +/* Specific graph card styling */ +.portfolio-graph { + border-left-color: var(--success); +} + +.cash-graph { + border-left-color: var(--info); +} + +.combined-graph { + border-left-color: var(--accent-primary); +} + +/* Mock chart styling for visual representation */ +.mock-chart { + width: 100%; + height: 100%; + background: linear-gradient(45deg, transparent 0%, var(--accent-primary) 20%, transparent 40%); + opacity: 0.1; + border-radius: 4px; + position: relative; + overflow: hidden; +} + +.mock-chart::before { + content: ''; + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 2px; + background: var(--accent-primary); + opacity: 0.6; + transform: translateY(-50%) rotate(-10deg); +} + +.mock-chart::after { + content: 'Chart data loading...'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--text-muted); + font-size: 0.9rem; + font-style: italic; +} + +/* Responsive design for graphs */ +@media (max-width: 768px) { + .graphs-header { + flex-direction: column; + gap: 15px; + align-items: stretch; + } + + .graphs-header h2 { + text-align: center; + } + + .time-period-selector { + justify-content: center; + } + + .graphs-grid { + grid-template-columns: 1fr; + gap: 15px; + } + + .graph-card { + padding: 15px; + } + + .graph-container { + height: 180px; + padding: 10px; + } + + .period-btn { + padding: 6px 12px; + font-size: 0.8rem; + } +} + +@media (max-width: 480px) { + .growth-graphs-section { + margin-top: 30px; + } + + .graphs-header h2 { + font-size: 1.3rem; + } + + .graph-header { + flex-direction: column; + align-items: flex-start; + gap: 10px; + margin-bottom: 15px; + } + + .graph-stats { + align-items: flex-start; + } + + .current-value { + font-size: 1.1rem; + } + + .graph-container { + height: 160px; + } +} + .dashboard-card.yearly-investment { border-left-color: var(--accent-purple); }