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:
@@ -308,11 +308,17 @@ struct ChartsContainerView: View {
|
||||
goals: goalsViewModel.goals
|
||||
)
|
||||
case .allocation:
|
||||
AllocationPieChart(
|
||||
data: viewModel.allocationData,
|
||||
title: viewModel.selectedBreakdown == .source ? "Allocation by Source" : "Asset Allocation",
|
||||
showsTargetsComparison: ChartsViewModel.supportsAllocationTargets(for: viewModel.selectedBreakdown)
|
||||
)
|
||||
VStack(spacing: 20) {
|
||||
AllocationPieChart(
|
||||
data: viewModel.allocationData,
|
||||
title: viewModel.selectedBreakdown == .source ? "Allocation by Source" : "Asset Allocation",
|
||||
showsTargetsComparison: ChartsViewModel.supportsAllocationTargets(for: viewModel.selectedBreakdown)
|
||||
)
|
||||
|
||||
if !viewModel.allocationEvolutionData.isEmpty {
|
||||
AllocationEvolutionChart(data: viewModel.allocationEvolutionData)
|
||||
}
|
||||
}
|
||||
case .performance:
|
||||
PerformanceBarChart(
|
||||
data: viewModel.performanceData,
|
||||
@@ -920,6 +926,70 @@ struct CashflowStackedChartView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Allocation Evolution Chart
|
||||
|
||||
struct AllocationEvolutionChart: View {
|
||||
let data: [(date: Date, category: String, percentage: Double, color: String)]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text("Allocation Over Time")
|
||||
.font(.headline)
|
||||
|
||||
if data.isEmpty {
|
||||
Text("Not enough data to show allocation evolution.")
|
||||
.foregroundColor(.secondary)
|
||||
.frame(height: 260)
|
||||
} else {
|
||||
Chart {
|
||||
ForEach(data, id: \.date.description + \.category) { item in
|
||||
AreaMark(
|
||||
x: .value("Date", item.date),
|
||||
y: .value("Percentage", item.percentage)
|
||||
)
|
||||
.foregroundStyle(by: .value("Category", item.category))
|
||||
}
|
||||
}
|
||||
.chartForegroundStyleScale(domain: categoryNames, range: categoryColors)
|
||||
.chartXAxis {
|
||||
AxisMarks(values: .stride(by: .month, count: 2)) { _ in
|
||||
AxisValueLabel(format: .dateTime.month(.abbreviated).year(.twoDigits))
|
||||
}
|
||||
}
|
||||
.chartYAxis {
|
||||
AxisMarks(position: .leading) { value in
|
||||
AxisValueLabel {
|
||||
if let pct = value.as(Double.self) {
|
||||
Text(String(format: "%.0f%%", pct))
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 260)
|
||||
.drawingGroup()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(AppConstants.UI.cornerRadius)
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
|
||||
}
|
||||
|
||||
private var categoryNames: [String] {
|
||||
Array(Set(data.map { $0.category })).sorted()
|
||||
}
|
||||
|
||||
private var categoryColors: [Color] {
|
||||
categoryNames.map { name in
|
||||
if let hex = data.first(where: { $0.category == name })?.color {
|
||||
return Color(hex: hex) ?? .gray
|
||||
}
|
||||
return .gray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ChartsContainerView(iapService: IAPService())
|
||||
.environmentObject(AccountStore(iapService: IAPService()))
|
||||
|
||||
Reference in New Issue
Block a user