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

SwiftUI Chart中如何复用月份列实现跨年份数据同列展示?

问题描述

我用以下Swift代码生成包含过去500天随机日期(chartDate)和随机销售额(sales)的数据集:

func dailySales2() -> [(chartDate: Date, year: String, sales: Int)] {
    var temp: [(chartDate: Date, year: String, sales: Int)] = []
    
    for _ in 1...500 {
        let dateT = Calendar.current.date(byAdding: .day, value: -Int.random(in: 1...500), to: Date())!
        temp.append((chartDate: dateT, year: dateT.formatted(.dateTime.year()), sales: Int.random(in: 100...500)))
    }
    
    return temp.sorted(by: { $0.chartDate < $1.chartDate })
}

当前用SwiftUI Chart展示数据的代码如下:

Chart {
    ForEach(dailySales2(), id: \.chartDate) { chartD in
        BarMark(
            x: .value("Day", chartD.chartDate, unit: .month),
            y: .value("Sales", chartD.sales)
        )
        .foregroundStyle(by: .value("Year:", chartD.year))
    }
}
.chartXAxis {
    AxisMarks(values: .stride(by: .month)) {
        AxisValueLabel(format: .dateTime.month(.narrow), centered: true)
    }
}

现在图表会生成2021年8月至2022年12月的月份列(A S O N D J F M A M J J A S O N D),我希望仅保留1-12月各一列,把不同年份的数据作为系列放在对应月份列中,该怎么实现?

解决方案

核心思路是将所有日期映射到同一个基准年份的对应月份,让不同年份的同月份数据对齐到X轴的同一位置,再通过年份区分系列。具体实现步骤如下:

1. 添加日期归一化工具方法

定义一个方法,将任意日期转换为基准年(比如2023年)的对应月份日期,确保同月份的日期都指向同一X轴位置:

func normalizedMonthDate(from date: Date) -> Date {
    let calendar = Calendar.current
    let month = calendar.component(.month, from: date)
    return calendar.date(from: DateComponents(year: 2023, month: month))!
}

2. 修改Chart的X轴绑定逻辑

将BarMark的X值替换为归一化后的日期,同时保持unit: .month,这样X轴只会生成1-12月的列:

Chart {
    ForEach(dailySales2(), id: \.chartDate) { chartD in
        BarMark(
            x: .value("Month", normalizedMonthDate(from: chartD.chartDate), unit: .month),
            y: .value("Sales", chartD.sales)
        )
        .foregroundStyle(by: .value("Year", chartD.year))
    }
}
.chartXAxis {
    AxisMarks(values: .stride(by: .month)) { value in
        if let date = value.as(Date.self) {
            AxisValueLabel(format: .dateTime.month(.narrow), centered: true)
        }
    }
}

3. 可选:聚合同月份销售额(避免数据重叠)

如果同一天存在多条数据,建议先聚合同月份同年份的总销售额,避免柱子重叠混乱:

func aggregatedSales() -> [(month: Int, year: String, totalSales: Int)] {
    let salesData = dailySales2()
    // 按(月份,年份)分组
    let grouped = Dictionary(grouping: salesData) { item in
        (Calendar.current.component(.month, from: item.chartDate), item.year)
    }
    // 计算每组的总销售额并排序
    return grouped.map { key, items in
        (month: key.0, year: key.1, totalSales: items.reduce(0) { $0 + $1.sales })
    }.sorted { $0.month < $1.month }
}

修改Chart的数据源为聚合后的数据:

Chart {
    ForEach(aggregatedSales(), id: \.month) { data in
        BarMark(
            x: .value("Month", Calendar.current.date(from: DateComponents(year: 2023, month: data.month))!, unit: .month),
            y: .value("Total Sales", data.totalSales)
        )
        .foregroundStyle(by: .value("Year", data.year))
    }
}
.chartXAxis {
    AxisMarks(values: .stride(by: .month)) { value in
        if let date = value.as(Date.self) {
            AxisValueLabel(format: .dateTime.month(.narrow), centered: true)
        }
    }
}

内容的提问来源于stack exchange,提问作者Gizza Gander

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 19:25:17