Swift Charts LineChart: 即使相应的γ值为nil,也要在x轴上显示日期

3
这是我第一次使用原生的Swift Charts框架,我在图表的x轴上遇到了一个问题。
首先,让我告诉你我想要实现的图表行为:
我想要渲染一个图表,显示最近七天(包括今天)的体重数据,类似于苹果健康应用程序在“周”选项卡中的显示方式。从苹果健康的截图中可以看到,x轴始终显示最近七天的日期,无论实际上是否有对应日期的数据。在左侧的截图中,你可以看到一个完整的星期,其中所有日期都有有效数据。在右侧的截图中,你可以看到一个完整的星期,但是最后一天(“今天”)的数据被删除了。苹果健康仍然正确地显示该日期在x轴上,只是不使用该数据点来绘制线状图,保持x轴固定为7天:
苹果健康应用程序中期望的x轴行为:

enter image description here

这是我尝试重新创建所描述的图表行为:
每个图表数据元素都有一个.date和可选的.averageWeight属性:
struct WeightChartData: Identifiable
{
    var id = UUID()
    var date: Date
    var averageWeight: Double?
}

我使用一个包含7个WeightChartData元素的数据数组,表示用户过去7天的体重数据(包括今天)。任何一天的体重数据可能为nill(当该天没有检索到HealthKit数据时)。数组被填充了数据,以便最后一个元素代表"今天"。以下是我的测试/调试测试数组(这样你可以更好地看到我的实际数据数组的结构):
var testData: [WeightChartData] =
[
    WeightChartData(date: Calendar.current.date(byAdding: .day, value: -6, to: Calendar.current.startOfDay(for: Date()))!, averageWeight: 95.1),
    WeightChartData(date: Calendar.current.date(byAdding: .day, value: -5, to: Calendar.current.startOfDay(for: Date()))!, averageWeight: 94.2),
    WeightChartData(date: Calendar.current.date(byAdding: .day, value: -4, to: Calendar.current.startOfDay(for: Date()))!, averageWeight: 94.4),
    WeightChartData(date: Calendar.current.date(byAdding: .day, value: -3, to: Calendar.current.startOfDay(for: Date()))!, averageWeight: nil),
    WeightChartData(date: Calendar.current.date(byAdding: .day, value: -2, to: Calendar.current.startOfDay(for: Date()))!, averageWeight: 93.5),
    WeightChartData(date: Calendar.current.date(byAdding: .day, value: -1, to: Calendar.current.startOfDay(for: Date()))!, averageWeight: 93.6),
    WeightChartData(date: Calendar.current.startOfDay(for: Date()),                                                        averageWeight: nil)
]

需要注意的是,数组中可能包含WeightChartData元素,其中.averageWeight == nill,表示这些天没有体重数据。
这是我尝试绘制图表的方式(WeightViewModel.Data 是我的“生产”数据数组,包含7个 WeightChartData 元素)。请注意条件语句 LineMark(...)
Chart(WeightViewModel.Data)
{ item in
    if item.averageWeight != nil
    {
        LineMark(
            x: .value("Date", item.date, unit: .weekday),
            y: .value("Value", item.averageWeight!)
        )
    }
}

我使用这两个修饰符来确保我的y轴刻度围绕数据数组的最小值和最大值.averageWeight。
.chartYScale(
    domain: [ getBottomRange(), getTopRange() ]
)

.chartYAxis
{
    AxisMarks(
        position: .leading,
        values: .automatic(desiredCount: getYMarkCount())
    )
}

这个(γ轴自定义)按预期工作。如果您认为这些可能导致我的x轴相关问题,还有`getBottomRange()`、`getTopRange()`和`getYMarkCount()`。
func getTopRange() -> Int
{
    return Int(round(WeightViewModel.maxWeightData()?.averageWeight ?? 0.0) + 1.0)
}
    
func getBottomRange() -> Int
{
    return Int(round(WeightViewModel.minWeightData()?.averageWeight ?? 0.0) - 1.0)
}

func getYMarkCount() -> Int
{        
    return abs(getTopRange() - getBottomRange())
}

对于x轴,我使用这个方法为每一天创建一个AxisMark(...)(所以x轴上有7个元素,不管包含重量数据的元素还是nil,就像在Apple Health中一样)。
.chartXAxis
{
    AxisMarks(values: .stride(by: .day))
    { date in
        AxisGridLine()
        AxisValueLabel(format: .dateTime.weekday(.narrow), centered: true)
    }
}

让我现在向您展示一下,如果有一天的.averageWeight == nil会发生什么情况:
  • 如果WeightViewModel.Data的所有元素都有有效的.averageWeight:图表将正确绘制(即在x轴上绘制所有7天):

enter image description here

如果一个元素(不是第一个或最后一个元素)的.averageWeight == nil,图表会正确地跳过这个y值,同时仍然在x轴上显示该天:

enter image description here

就像在Apple Health中一样。在这个例子中,只有倒数第二个元素的.averageWeight == nil

  • 问题是当第一个或最后一个元素的.averageWeight == nil时。同样,因为LineMark(..)只有在item.averageWeight != nil时才会被调用,所以这个第一个或最后一个数据点会被跳过。但这一次,由于它是第一个或最后一个元素,我的x轴刻度会被打乱。它不再显示7天,而是从图表中删除没有weightData的那一天,结果只剩下6天的x轴:

enter image description here

在这个例子中,我将最后一个元素的.averageWeight设置为nil请注意,星期四不再显示在x轴上
这并不太令人惊讶,因为当第一个或最后一个元素被跳过时,图表甚至不知道它必须显示7个日期。但是我仍然希望没有.averageWeight的那一天显示在图表的x轴上(因为我的x轴应该始终有7个元素代表最近7天)。
如何实现所描述的图表行为,即在可能为nil的可选数据存在时始终显示完整的一周?
感谢您阅读所有内容并尝试帮助我。
1个回答

2

就像我说的,我现在只用了几周的Swift/SwiftUI,但显然你可以在闭合日期范围内使用.chartXScale(domain:)...

domain: 图表中x轴上可能的数据值。你可以使用ClosedRange来定义数字或日期值的域(例如,0 ... 500)... - 文档

奇怪的是,我只有在最后一个元素后面添加一天后才得到了我想要的行为:

.chartXScale(domain: WeightViewModel.Data.first!.date ... Calendar.current.date(byAdding: .day, value: 1, to: WeightViewModel.Data.last!.date)!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接