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

SwiftUI Charts问题:X轴数据点默认间距不均如何解决?

解决Swift Charts中X轴日期点间距不均的问题

问题描述

我正在为应用实现一款追踪游戏利润变化的折线图,用于展示年度利润进度。目前使用模拟数据(涵盖2023年1月、2月、3月、5月、12月的日期)调试图表样式,但因X轴数据点间距不均,导致图表显示异常。

原始代码:

let dummyData: [dummySession] = [
    .init(date: Date.from(year: 2023, month: 1, day: 1), profit: 0),
    .init(date: Date.from(year: 2023, month: 2, day: 12), profit: 55),
    .init(date: Date.from(year: 2023, month: 3, day: 20), profit: 376),
    .init(date: Date.from(year: 2023, month: 5, day: 2), profit: 120),
    .init(date: Date.from(year: 2023, month: 5, day: 18), profit: 500),
    .init(date: Date.from(year: 2023, month: 12, day: 11), profit: 222)
]

VStack {
    Chart {
        ForEach(dummyData) { session in
            LineMark(x: .value("Date", session.date, unit: .month),
                     y: .value("Profit", session.profit))
            .lineStyle(.init(lineWidth: 3, lineCap: .round, lineJoin: .round))
        }
        .interpolationMethod(.cardinal)
    }
    .chartXAxis {
        AxisMarks(stroke: StrokeStyle(lineWidth: 0))
    }
    .chartYAxis {
        AxisMarks(position: .trailing, values: .automatic) { value in
            AxisGridLine()
            AxisValueLabel() {
                if let intValue = value.as(Int.self) {
                    Text(intValue.asCurrency())
                        .padding(.leading, 25)
                }
            }
        }
    }
}
.frame(maxHeight: 300)

图表效果:
异常的折线图

问题原因

当前代码中LineMark的x轴指定了unit: .month,Swift Charts会将日期按真实时间跨度排列,导致5月的两个点挤在一起,12月与前一个点间隔7个月,出现间距严重不均的情况。

解决方案

方案1:按数据顺序均匀排列(忽略真实时间间隔)

如果只需要展示数据的先后顺序,不需要体现真实时间跨度,可以将日期转换为分类值(比如字符串),让X轴均匀分配每个数据点的间距:

VStack {
    Chart {
        // 遍历数据时格式化日期为短月份标签
        ForEach(dummyData) { session in
            let dateLabel = session.date.formatted(.dateTime.month(.abbreviated))
            LineMark(x: .value("Date", dateLabel),
                     y: .value("Profit", session.profit))
            .lineStyle(.init(lineWidth: 3, lineCap: .round, lineJoin: .round))
        }
        .interpolationMethod(.cardinal)
    }
    .chartXAxis {
        AxisMarks(stroke: StrokeStyle(lineWidth: 0))
    }
    .chartYAxis {
        AxisMarks(position: .trailing, values: .automatic) { value in
            AxisGridLine()
            AxisValueLabel() {
                if let intValue = value.as(Int.self) {
                    Text(intValue.asCurrency())
                        .padding(.leading, 25)
                }
            }
        }
    }
}
.frame(maxHeight: 300)

方案2:保留真实时间轴,优化显示范围

如果需要体现真实时间跨度,可通过以下两种方式优化:

子方案A:补充缺失月份的默认数据

生成2023年1-12月的完整数据序列,为缺失月份添加默认利润值(如0),让时间轴连续:

// 生成全年完整数据
var fullYearData: [dummySession] = []
let calendar = Calendar.current
for month in 1...12 {
    // 查找当前月份的已有数据
    if let existingSession = dummyData.first(where: {
        calendar.component(.month, from: $0.date) == month
    }) {
        fullYearData.append(existingSession)
    } else {
        // 为缺失月份添加默认数据
        let defaultDate = Date.from(year: 2023, month: month, day: 1)
        fullYearData.append(.init(date: defaultDate, profit: 0))
    }
}

// 使用完整数据绘制图表
VStack {
    Chart {
        ForEach(fullYearData) { session in
            LineMark(x: .value("Date", session.date, unit: .month),
                     y: .value("Profit", session.profit))
            .lineStyle(.init(lineWidth: 3, lineCap: .round, lineJoin: .round))
        }
        .interpolationMethod(.cardinal)
    }
    .chartXAxis {
        AxisMarks(stroke: StrokeStyle(lineWidth: 0))
    }
    .chartYAxis {
        AxisMarks(position: .trailing, values: .automatic) { value in
            AxisGridLine()
            AxisValueLabel() {
                if let intValue = value.as(Int.self) {
                    Text(intValue.asCurrency())
                        .padding(.leading, 25)
                }
            }
        }
    }
}
.frame(maxHeight: 300)

子方案B:设置X轴固定时间范围

直接指定X轴的时间范围为2023全年,让图表自动适配完整年度跨度,避免局部间隔过大:

VStack {
    Chart {
        ForEach(dummyData) { session in
            LineMark(x: .value("Date", session.date, unit: .month),
                     y: .value("Profit", session.profit))
            .lineStyle(.init(lineWidth: 3, lineCap: .round, lineJoin: .round))
        }
        .interpolationMethod(.cardinal)
    }
    .chartXScale(domain: Date.from(year: 2023, month: 1, day: 1)...Date.from(year: 2023, month: 12, day: 31))
    .chartXAxis {
        AxisMarks(stroke: StrokeStyle(lineWidth: 0))
    }
    .chartYAxis {
        AxisMarks(position: .trailing, values: .automatic) { value in
            AxisGridLine()
            AxisValueLabel() {
                if let intValue = value.as(Int.self) {
                    Text(intValue.asCurrency())
                        .padding(.leading, 25)
                }
            }
        }
    }
}
.frame(maxHeight: 300)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 08:00:35