如何使用Apple的SwiftUI图表创建所需的图表?

Desired Chart(实体模型)-包含数据点的阶梯形曲线图为蓝色,趋势线为红色:

enter image description here

需要将趋势线添加到包含阶梯线图的现有图表中.除了使用另一个LineMark之外,SwiftUI图表标记似乎不适用于倾斜趋势线.

下面提供的趋势线代码仅在与散点图(PointMark)一起使用时有效.但当趋势线与线性图(即另一个LineMark)一起使用时,趋势线就会成为第一个LineMark的延续.当使用两个LineMark时,第二个LineMark上的修改器将被忽略.

Unwanted Result -当在SwiftUI图表中使用两个LineMarks时,这是不需要的结果,而不是上面的图表:

enter image description here

然而,当删除另一个LineMark时,趋势线代码会按照预期工作:

enter image description here

包含示例数据的完整示例代码:

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)
]

解决方案需要使用可滚动图表,因为最终图表将使用很长的时间表.(包含可滚动修饰符,但已被注释)

在线性图上try 了不同的插值方法,将数据分成两个数组,使用图表叠加并使用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)
    }
}

Swift相关问答推荐

如何使用swift宏生成一个带有关联值的枚举?

如何返回JSON数据?

从AppKit打开SwiftUI设置

修改数组中每个类实例的属性

类型擦除`AsyncMapSequence, Element>`

PassthroughSubject 的 AsyncPublisher 值属性未生成所有值

确定路径是否相交的有效方法

用逻辑运算符保护让

您是否必须手动指定您的 DispatchQueue 是串行的?

在 Vapor 4 中使用协议的通用流利查询

这是 Int64 Swift Decoding 多余的吗?

自定义选取器的出现导致其他视图重新排列

对齐时如何避免图像尺寸缩小

Swift UI 不重绘 NSViewRepresentable 包装的文本字段

试图同时实现两个混合过渡

在视图中设置所有变量

What is "SwiftPM.SPMRepositoryError error 5"?

Swift 中的 PageViewController 当前页面索引

如何轻松删除领域中的所有对象

在 Swift 中从服务器播放视频文件