import SwiftUI import Charts struct PerformanceBarChart: View { let data: [(category: String, cagr: Double, color: String)] var body: some View { VStack(alignment: .leading, spacing: 16) { Text("Performance by Category") .font(.headline) Text("Compound Annual Growth Rate (CAGR)") .font(.caption) .foregroundColor(.secondary) if !data.isEmpty { Chart(data, id: \.category) { item in BarMark( x: .value("Category", item.category), y: .value("CAGR", item.cagr) ) .foregroundStyle(Color(hex: item.color) ?? .gray) .cornerRadius(4) .annotation(position: item.cagr >= 0 ? .top : .bottom) { Text(String(format: "%.1f%%", item.cagr)) .font(.caption2) .foregroundColor(item.cagr >= 0 ? .positiveGreen : .negativeRed) } } .chartXAxis { AxisMarks { value in AxisValueLabel { if let category = value.as(String.self) { Text(category) .font(.caption) .lineLimit(1) } } } } .chartYAxis { AxisMarks { value in AxisGridLine() AxisValueLabel { if let doubleValue = value.as(Double.self) { Text(String(format: "%.0f%%", doubleValue)) .font(.caption) } } } } .frame(height: 250) // Legend / Details VStack(spacing: 8) { ForEach(data.sorted(by: { $0.cagr > $1.cagr }), id: \.category) { item in HStack { Circle() .fill(Color(hex: item.color) ?? .gray) .frame(width: 10, height: 10) Text(item.category) .font(.subheadline) Spacer() Text(String(format: "%.2f%%", item.cagr)) .font(.subheadline.weight(.semibold)) .foregroundColor(item.cagr >= 0 ? .positiveGreen : .negativeRed) } } } .padding(.top, 8) } else { Text("No performance data available") .foregroundColor(.secondary) .frame(height: 250) .frame(maxWidth: .infinity) } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } } // MARK: - Horizontal Bar Version struct HorizontalPerformanceChart: View { let data: [(category: String, cagr: Double, color: String)] var sortedData: [(category: String, cagr: Double, color: String)] { data.sorted { $0.cagr > $1.cagr } } var maxValue: Double { max(abs(data.map { $0.cagr }.max() ?? 0), abs(data.map { $0.cagr }.min() ?? 0)) } var body: some View { VStack(alignment: .leading, spacing: 16) { Text("Performance by Category") .font(.headline) ForEach(sortedData, id: \.category) { item in VStack(alignment: .leading, spacing: 4) { HStack { Text(item.category) .font(.subheadline) Spacer() Text(String(format: "%.2f%%", item.cagr)) .font(.subheadline.weight(.semibold)) .foregroundColor(item.cagr >= 0 ? .positiveGreen : .negativeRed) } GeometryReader { geometry in let normalizedValue = maxValue > 0 ? abs(item.cagr) / maxValue : 0 let barWidth = geometry.size.width * normalizedValue ZStack(alignment: item.cagr >= 0 ? .leading : .trailing) { Rectangle() .fill(Color.gray.opacity(0.1)) .frame(height: 8) .cornerRadius(4) Rectangle() .fill(item.cagr >= 0 ? Color.positiveGreen : Color.negativeRed) .frame(width: barWidth, height: 8) .cornerRadius(4) } } .frame(height: 8) } } } .padding() .background(Color(.systemBackground)) .cornerRadius(AppConstants.UI.cornerRadius) .shadow(color: .black.opacity(0.05), radius: 8, y: 2) } } #Preview { let sampleData: [(category: String, cagr: Double, color: String)] = [ ("Stocks", 12.5, "#10B981"), ("Bonds", 4.2, "#3B82F6"), ("Real Estate", 8.1, "#F59E0B"), ("Crypto", -5.3, "#8B5CF6") ] return VStack(spacing: 20) { PerformanceBarChart(data: sampleData) HorizontalPerformanceChart(data: sampleData) } .padding() }