Add allocation evolution chart showing allocation changes over time
Adds a stacked area chart under the Allocation tab that displays how the portfolio allocation percentages have changed across months. Fixes #25 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -103,6 +103,7 @@ class ChartsViewModel: ObservableObject {
|
||||
@Published var drawdownData: [(date: Date, drawdown: Double)] = []
|
||||
@Published var volatilityData: [(date: Date, volatility: Double)] = []
|
||||
@Published var predictionData: [Prediction] = []
|
||||
@Published var allocationEvolutionData: [(date: Date, category: String, percentage: Double, color: String)] = []
|
||||
|
||||
@Published var isLoading = false
|
||||
@Published var showingPaywall = false
|
||||
@@ -340,6 +341,7 @@ class ChartsViewModel: ObservableObject {
|
||||
calculateCategoryEvolutionData(from: completedSnapshots, categories: categoriesForChart)
|
||||
case .allocation:
|
||||
calculateAllocationData(for: sources, breakdown: selectedBreakdown)
|
||||
calculateAllocationEvolutionData(from: completedSnapshots)
|
||||
case .performance:
|
||||
let completedSnapshotsBySource = groupSnapshotsBySource(completedSnapshots)
|
||||
calculatePerformanceData(
|
||||
@@ -587,6 +589,57 @@ class ChartsViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateAllocationEvolutionData(from snapshots: [Snapshot]) {
|
||||
let sortedSnapshots = snapshots.sorted { $0.date < $1.date }
|
||||
let groupedByMonth = Dictionary(grouping: sortedSnapshots) { snapshot -> DateComponents in
|
||||
Calendar.current.dateComponents([.year, .month], from: snapshot.date)
|
||||
}
|
||||
|
||||
let sortedMonths = groupedByMonth.keys.sorted {
|
||||
let d1 = ($0.year ?? 0) * 100 + ($0.month ?? 0)
|
||||
let d2 = ($1.year ?? 0) * 100 + ($1.month ?? 0)
|
||||
return d1 < d2
|
||||
}
|
||||
|
||||
var result: [(date: Date, category: String, percentage: Double, color: String)] = []
|
||||
|
||||
for monthKey in sortedMonths {
|
||||
guard let monthSnapshots = groupedByMonth[monthKey],
|
||||
let monthDate = Calendar.current.date(from: monthKey) else { continue }
|
||||
|
||||
var categoryTotals: [String: (value: Decimal, color: String)] = [:]
|
||||
var sourceLatest: [UUID: Snapshot] = [:]
|
||||
|
||||
for snapshot in monthSnapshots {
|
||||
guard let sourceId = snapshot.source?.id else { continue }
|
||||
if let existing = sourceLatest[sourceId] {
|
||||
if snapshot.date > existing.date {
|
||||
sourceLatest[sourceId] = snapshot
|
||||
}
|
||||
} else {
|
||||
sourceLatest[sourceId] = snapshot
|
||||
}
|
||||
}
|
||||
|
||||
for (_, snapshot) in sourceLatest {
|
||||
let categoryName = snapshot.source?.category?.name ?? "Other"
|
||||
let colorHex = snapshot.source?.category?.colorHex ?? "#6B7280"
|
||||
let existing = categoryTotals[categoryName] ?? (value: 0, color: colorHex)
|
||||
categoryTotals[categoryName] = (value: existing.value + snapshot.decimalValue, color: colorHex)
|
||||
}
|
||||
|
||||
let total = categoryTotals.values.reduce(Decimal.zero) { $0 + $1.value }
|
||||
guard total > 0 else { continue }
|
||||
|
||||
for (category, info) in categoryTotals.sorted(by: { $0.value.value > $1.value.value }) {
|
||||
let percentage = NSDecimalNumber(decimal: info.value / total * 100).doubleValue
|
||||
result.append((date: monthDate, category: category, percentage: percentage, color: info.color))
|
||||
}
|
||||
}
|
||||
|
||||
allocationEvolutionData = result
|
||||
}
|
||||
|
||||
private func completedMonthKeys(
|
||||
sources: [InvestmentSource],
|
||||
snapshots: [Snapshot],
|
||||
|
||||
Reference in New Issue
Block a user