我正在使用 Swift Charts 构建折线图来绘制健康数据。 下面是我的代码,其中包含一些用于生成演示的模拟数据:
import SwiftUI
import Charts
struct Demo: View {
struct HR: Identifiable {
let day: Date
let value: Int
var id: Date {
day
}
}
let mockData: [HR]
init() {
func generateMockData() -> [HR] {
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
var data: [HR] = []
for i in 0...27 {
let day = calendar.date(byAdding: .day, value: -i, to: today)
let value = Int.random(in: 50...100)
let hr: HR = .init(day: day!, value: value)
data.append(hr)
}
return data
}
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
self.mockData = generateMockData()
let beginningOfInterval = today.addingTimeInterval(-1 * 7 * 24 * 3600)
self._scrollPosition = State(initialValue: beginningOfInterval.timeIntervalSince1970)
}
@State private var scrollPosition: TimeInterval = TimeInterval()
var body: some View {
Chart(mockData) { hr in
LineMark(
x: .value("Day", hr.day, unit: .day),
y: .value("Value", hr.value)
)
.symbol(.circle)
}
.chartXAxis{
AxisMarks(values: .stride(by: .day)) { _ in
AxisValueLabel(
format: .dateTime.weekday(),
centered: true
)
AxisGridLine()
}
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 7)
.chartScrollTargetBehavior(
.valueAligned(
matching: .init(hour: 0),
majorAlignment: .matching(.init(weekday: 1)),
limitBehavior: .never
)
)
.scrollTargetBehavior(.viewAligned)
.chartScrollPosition(x: $scrollPosition)
.aspectRatio(1, contentMode: .fit)
.padding()
}
}
#Preview {
Demo()
}
如您所见,我已使图表可滚动,并在停止滚动时捕捉到星期日。
问题是,当最后一天不是星期六时,我无法滚动图表以在可见区域显示自然周,最新值总是在图表的右边缘附近:
在Apple的健康应用程序中,您始终可以滚动以将当前的自然周显示在可见区域中,并且今天之后的日子将由空槽表示:
另一个有趣的细节是,在健康应用程序中,图表可以进一步推动(按住触摸)以显示代表更多未来日子的空间,并且网格线似乎无限延伸。
我想知道是否可以使用 Swift Charts 实现与 Health App 相同的功能,特别是显示当前自然周/月?
找到部分解决方案:
我使用
.chartXScale(domain:)
设置开始和结束日期,尽管它们最初可能位于可见区域之外。
现在代码如下所示:
import SwiftUI
import Charts
struct Demo: View {
struct HR: Identifiable {
let day: Date
let value: Int
var id: Date {
day
}
}
let mockData: [HR]
init() {
func generateMockData() -> [HR] {
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
var data: [HR] = []
for i in 0...27 {
let day = calendar.date(byAdding: .day, value: -i, to: today)
let value = Int.random(in: 50...100)
let hr: HR = .init(day: day!, value: value)
data.append(hr)
}
return data
}
self.mockData = generateMockData()
}
@State private var scrollPosition: TimeInterval = TimeInterval()
var body: some View {
let calendar = Calendar.current
let today = calendar.startOfDay(for: Date())
let weekday = calendar.component(.weekday, from: today)
let daysToSubtract = weekday - calendar.firstWeekday
let startOfWeek = calendar.date(byAdding: .day, value: -daysToSubtract, to: today)
let start = calendar.date(byAdding: .day, value: -30, to: today)
let end = calendar.date(byAdding: .day, value: 7, to: today)
Chart(mockData) { hr in
LineMark(
x: .value("Day", hr.day, unit: .day),
y: .value("Value", hr.value)
)
.symbol(.circle)
}
.chartXAxis{
AxisMarks(preset: .aligned, values: .stride(by: .day)) { _ in
AxisValueLabel(
format: .dateTime.weekday(),
centered: true
)
AxisGridLine()
}
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 3600 * 24 * 7)
.chartXScale(domain: start!...end!)
.chartScrollTargetBehavior(
.valueAligned(
matching: .init(weekday: 1),
majorAlignment: .matching(.init(weekday: 1)),
limitBehavior: .always
)
)
.chartScrollPosition(initialX: startOfWeek!)
.chartScrollPosition(x: $scrollPosition)
.aspectRatio(1, contentMode: .fit)
.padding()
}
}
#Preview {
Demo()
}
我可以使用
.chartScrollPosition(initialX:)
来设置图表的初始位置。
现在剩下的问题是,在图表末尾按住不放,如何在未来几天显示无限网格线。