如何使用 Apple 的 SwiftUI 图表创建所需的图表?
所需图表(模型)-带有数据点的阶梯线图为蓝色,趋势线为红色:
需要将趋势线添加到包含阶梯线图的现有图表中。除了使用另一个 LineMark 之外,似乎没有 SwiftUI 图表标记适用于倾斜趋势线。
下面提供的趋势线代码仅在与散点图(PointMark)一起使用时才有效。但是,当趋势线与折线图(即另一个 LineMark)一起使用时,趋势线将成为第一个 LineMark 的延续。当使用两个 LineMark 时,第二个 LineMark 上的修饰符将被忽略。
不需要的结果 - 在 SwiftUI 图表中使用两个 LineMark 时,这是不需要的结果,而不是上面的图表:
然而,当删除其他 LineMark 时,趋势线代码可以按预期工作:
带有示例数据的完整示例代码:
import SwiftUI
import Charts
struct ContentView: View {
@State var showSteps: Bool = false
@State var showTrend: Bool = false
var body: some View {
VStack {
Chart {
//Main data
ForEach(dataPoints.filter({ data in data.type == .data}), id: \.id) { point in
if showSteps {
LineMark(
x: PlottableValue.value("Date", point.date),
y: PlottableValue.value("Amount", point.amount)
)
.interpolationMethod(.stepEnd)
}
PointMark(
x: .value("Date", point.date),
y: .value("Amount", point.amount)
)
}
// Trend line
if showTrend {
ForEach(dataPoints.filter({ data in data.type == .trend}), id: \.id) { trend in
LineMark(
x: PlottableValue.value("Date", trend.date),
y: PlottableValue.value("Amount", trend.amount)
)
.foregroundStyle(Color.red)
.lineStyle(StrokeStyle(lineWidth: 5))
.interpolationMethod(.linear)
}
}
}
.padding(.all)
.frame(height: 500)
.chartXScale(domain: Date(timeIntervalSinceNow: 3600 * 24 * -15) ... Date(timeIntervalSinceNow: 3600 * 24 * 15))
.chartYAxis {
AxisMarks(preset: .aligned, position: .automatic, values: .stride(by: 5)) {
let value = $0.as(Int.self) ?? 0
AxisGridLine()
AxisValueLabel(String("\(value)"))
}
}
//.chartScrollableAxes(.horizontal) // future use
Group {
Toggle("Show Steps", isOn: $showSteps)
Toggle("Show Trend", isOn: $showTrend)
}.padding(.horizontal)
}
}
}
enum DataType {
case data
case trend
}
struct DataPoint: Hashable, Identifiable {
var id : UUID = UUID()
var type : DataType
var date : Date
var amount : Double
}
let dataPoints: [DataPoint] = [
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -14), amount: 100),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -13), amount: 97),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -10), amount: 85),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -9), amount: 84),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -8), amount: 82),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -7), amount: 78),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -6), amount: 75),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -5), amount: 68),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -4), amount: 65),
DataPoint(id: UUID(), type: .data, date: Date(timeIntervalSinceNow: 3600 * 24 * -3), amount: 50),
DataPoint(id: UUID(), type: .data, date: Date(), amount: 48),
DataPoint(id: UUID(), type: .trend, date: Date(timeIntervalSinceNow: 3600 * 24 * -15), amount: 100),
DataPoint(id: UUID(), type: .trend, date: Date(timeIntervalSinceNow: 3600 * 24 * 15), amount: 0)
]
解决方案需要使用可滚动图表,因为最终图表将使用较大的时间范围。 (包含可滚动修饰符但已注释掉)
在线性图上尝试不同的插值方法,将数据分成两个数组,使用图表叠加并使用 CGPath 创建趋势线。其中之一可能是一个解决方案,但我可能错过了如何正确设置它。
关于如何让所需的趋势线与另一个 LineMark 正确配合,有什么建议吗?或者如何在不使用 LineMark 的情况下创建趋势线?
趋势线和实际数据的线标记应该处于不同的系列。否则,它们被认为是在同一个系列中,并且点将连接在一起。
添加
series:
参数并赋予它们不同的 PlottableValue
。
if showSteps {
LineMark(
x: .value("Date", point.date),
y: .value("Amount", point.amount),
series: .value("Series", "Actual Data")
)
.interpolationMethod(.stepEnd)
}
if showTrend {
ForEach(dataPoints.filter({ data in data.type == .trend}), id: \.id) { trend in
LineMark(
x: .value("Date", trend.date),
y: .value("Amount", trend.amount),
series: .value("Series", "Trend")
)
.foregroundStyle(Color.red)
.lineStyle(StrokeStyle(lineWidth: 5))
.interpolationMethod(.linear)
}
}