Как я могу создать эту желаемую диаграмму с помощью диаграмм SwiftUI от Apple?
Желаемая диаграмма (макет). Ступенчатый линейный график с точками данных выделен синим цветом, а линия тренда — красным:
Линию тренда необходимо добавить к существующей диаграмме, содержащей ступенчатый линейный график. Никакие отметки SwiftUI Chart, похоже, не применимы к наклонной линии тренда, кроме использования другого LineMark.
Код линии тренда, представленный ниже, работает только при использовании с диаграммой рассеяния (PointMark). Но когда линия тренда используется с линейным графиком, то есть с другим LineMark, тогда линия тренда становится продолжением первого LineMark. Когда используются два LineMark, модификаторы второго LineMark игнорируются.
Нежелательный результат. При использовании двух линейных отметок в диаграммах SwiftUI это нежелательный результат вместо диаграммы выше:
Тем не менее, код линии тренда работает так, как хотелось бы, когда другой 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)
}
}
ОЙ!! Оно работает. Не осознавал, что сериал так работает и нужен. Я думал, что фильтра будет достаточно. Я бы никогда не добрался туда без вашей помощи. Спасибо.