如何用Swift Charts为理财APP实现动态时间维度柱状图
理财APP支出分类柱状图实现方案(按时段聚合)
一、核心逻辑拆解
要实现按「今日/本周/本月/本年」不同时段展示分类支出柱状图,核心是完成时间范围筛选→按维度分组聚合→X轴标签匹配三个步骤,以下是具体Swift实现:
1. 定义时段枚举(统一时间规则)
先把各时段的时间范围、分组维度、标签格式封装成枚举,避免重复代码:
enum TimeRange { case today, thisWeek, thisMonth, thisYear // 获取时段的起止日期 func dateRange() -> (start: Date, end: Date) { let calendar = Calendar.current switch self { case .today: let start = calendar.startOfDay(for: Date()) let end = calendar.date(byAdding: .day, value: 1, to: start)! return (start, end) case .thisWeek: let start = calendar.date(from: calendar.dateComponents([.yearForWeekOfYear, .weekOfYear], from: Date()))! let end = calendar.date(byAdding: .weekOfYear, value: 1, to: start)! return (start, end) case .thisMonth: let start = calendar.date(from: calendar.dateComponents([.year, .month], from: Date()))! let end = calendar.date(byAdding: .month, value: 1, to: start)! return (start, end) case .thisYear: let start = calendar.date(from: calendar.dateComponents([.year], from: Date()))! let end = calendar.date(byAdding: .year, value: 1, to: start)! return (start, end) } } // 获取分组的时间单位+X轴标签格式 func groupingRule() -> (unit: Calendar.Component, labelFormat: String) { switch self { case .today: return (.hour, "HH") // 24小时制,如"14" case .thisWeek: return (.weekday, "EEE") // 星期缩写,如"周一"/"Mon" case .thisMonth: return (.day, "d") // 日期数字,如"5" case .thisYear: return (.month, "MMM") // 月份缩写,如"一月"/"Jan" } } }
2. 交易数据分组聚合
假设你的交易模型是Transaction,包含category(分类)、amount(金额)、date(交易日期)字段,以下代码完成数据筛选和按分类+时间节点的金额累加:
struct Transaction { let category: String let amount: Double let date: Date } // 生成对应时段的所有时间节点(比如今日的0-23点、本周的7天) func generateTimeNodes(for range: TimeRange) -> [Date] { let (start, end) = range.dateRange() let (unit, _) = range.groupingRule() let calendar = Calendar.current var nodes: [Date] = [] var currentDate = start while currentDate < end { nodes.append(currentDate) guard let nextDate = calendar.date(byAdding: unit, value: 1, to: currentDate) else { break } currentDate = nextDate } return nodes } // 按时段+分类聚合支出数据 func aggregateExpenseData(_ transactions: [Transaction], for range: TimeRange) -> [String: [Double]] { let (start, end) = range.dateRange() let (unit, _) = range.groupingRule() let calendar = Calendar.current // 筛选时段内的支出交易(假设支出金额为负,根据你的模型调整) let filteredExpenses = transactions.filter { $0.date >= start && $0.date < end && $0.amount < 0 } let timeNodes = generateTimeNodes(for: range) let categories = Set(filteredExpenses.map { $0.category }) // 初始化所有分类的时间节点金额为0 var categoryAmounts: [String: [Double]] = [:] for category in categories { categoryAmounts[category] = Array(repeating: 0.0, count: timeNodes.count) } // 遍历交易,累加对应分类+时间节点的支出金额 for expense in filteredExpenses { guard let nodeIndex = timeNodes.firstIndex(where: { node in calendar.isDate(expense.date, equalTo: node, toGranularity: unit) }) else { continue } categoryAmounts[expense.category]?[nodeIndex] += abs(expense.amount) } return categoryAmounts }
3. 生成X轴标签
根据时段生成对应显示文本,适配本地语言:
func generateXAxisLabels(for range: TimeRange) -> [String] { let timeNodes = generateTimeNodes(for: range) let (_, labelFormat) = range.groupingRule() let formatter = DateFormatter() formatter.locale = Locale.current // 自动适配系统语言 formatter.dateFormat = labelFormat return timeNodes.map { formatter.string(from: $0) } }
二、柱状图配置(以Charts库为例)
如果用iOS常用的Charts库,以下代码完成图表渲染配置:
import Charts func setupBarChart(_ chartView: BarChartView, aggregatedData: [String: [Double]], xLabels: [String]) { var dataSets: [BarChartDataSet] = [] // 为每个分类创建数据集 for (category, amounts) in aggregatedData { let entries = amounts.enumerated().map { index, amount in BarChartDataEntry(x: Double(index), y: amount) } let dataSet = BarChartDataSet(entries: entries, label: category) // 可自定义分类颜色,这里示例为随机色 dataSet.setColor(UIColor(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: 0.8)) dataSets.append(dataSet) } // 配置图表数据 let chartData = BarChartData(dataSets: dataSets) chartView.data = chartData // 配置X轴 let xAxis = chartView.xAxis xAxis.valueFormatter = IndexAxisValueFormatter(values: xLabels) xAxis.labelPosition = .bottom xAxis.granularity = 1 // 强制显示所有标签 xAxis.labelRotationAngle = -45 // 标签过多时旋转避免重叠 // 其他优化配置 chartView.legend.position = .top chartView.rightAxis.enabled = false chartView.leftAxis.axisMinimum = 0 // Y轴从0开始,更直观 chartView.leftAxis.labelFont = .systemFont(ofSize: 12) }
三、调用流程
用户切换时段时,执行以下代码更新图表:
// 示例:选中「今日」时段 let selectedRange: TimeRange = .today // 你的交易数据源(从数据库/接口获取) let allTransactions: [Transaction] = [] // 聚合数据+生成标签+更新图表 let aggregatedData = aggregateExpenseData(allTransactions, for: selectedRange) let xLabels = generateXAxisLabels(for: selectedRange) setupBarChart(yourBarChartView, aggregatedData: aggregatedData, xLabels: xLabels)
关键注意事项
- 周起始日适配:如果需要统一周起始为周一,可设置
Calendar.current.firstWeekday = 2(默认1为周日)。 - 空数据处理:如果某个时段没有支出,要确保图表显示全0的柱状图,避免空白。
- 性能优化:如果交易数据量极大,可在数据库层面完成分组聚合,减少内存计算。
内容的提问来源于stack exchange,提问作者Farhad Maleki
相关产品推荐
相关产品推荐

