You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 09:09:57