diff --git a/PortfolioJournal/ViewModels/ChartsViewModel.swift b/PortfolioJournal/ViewModels/ChartsViewModel.swift index d3ca1bb..4b0071d 100644 --- a/PortfolioJournal/ViewModels/ChartsViewModel.swift +++ b/PortfolioJournal/ViewModels/ChartsViewModel.swift @@ -294,8 +294,7 @@ class ChartsViewModel: ObservableObject { let sources: [InvestmentSource] if !selectedSourceIds.isEmpty { sources = sourceRepository.sources.filter { source in - guard let id = source.id else { return false } - return selectedSourceIds.contains(id) && shouldIncludeSource(source) + selectedSourceIds.contains(source.id) && shouldIncludeSource(source) } } else if let selectedSource { sources = sourceRepository.sources.filter { $0.id == selectedSource.id && shouldIncludeSource($0) } diff --git a/PortfolioJournal/Views/Charts/ChartsContainerView.swift b/PortfolioJournal/Views/Charts/ChartsContainerView.swift index 4d86335..780ebc8 100644 --- a/PortfolioJournal/Views/Charts/ChartsContainerView.swift +++ b/PortfolioJournal/Views/Charts/ChartsContainerView.swift @@ -929,46 +929,38 @@ struct CashflowStackedChartView: View { // MARK: - Allocation Evolution Chart +struct AllocationEvolutionDataPoint: Identifiable { + let id: String + let date: Date + let category: String + let percentage: Double + let color: String +} + struct AllocationEvolutionChart: View { let data: [(date: Date, category: String, percentage: Double, color: String)] + private var identifiableData: [AllocationEvolutionDataPoint] { + data.enumerated().map { index, item in + AllocationEvolutionDataPoint( + id: "\(index)-\(item.category)", + date: item.date, + category: item.category, + percentage: item.percentage, + color: item.color + ) + } + } + 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) + emptyView } 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() + chartView } } .padding() @@ -977,6 +969,40 @@ struct AllocationEvolutionChart: View { .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } + private var emptyView: some View { + Text("Not enough data to show allocation evolution.") + .foregroundColor(.secondary) + .frame(height: 260) + } + + private var chartView: some View { + Chart(identifiableData) { 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() + } + private var categoryNames: [String] { Array(Set(data.map { $0.category })).sorted() }