如何在折线图中添加趋势线?

问题描述 投票:0回答:1

如何使用 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 的情况下创建趋势线?

swift swiftui swiftui-charts
1个回答
0
投票

趋势线和实际数据的线标记应该处于不同的系列。否则,它们被认为是在同一个系列中,并且点将连接在一起。

添加

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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.