Currently with the below default configuration from SwiftUI Charts, it is plotting top to bottom, i.e. minimum to maximum top to bottom. See the attached picture. I want the axis flipped so its plotting bottom to top.
struct HeartRateChart: View {
let dateInterval: DateInterval
let segments: [HeartRateSegment]
let maxHeartRate: CGFloat
let minHeartRate: CGFloat
init(dateInterval: DateInterval, segments: [HeartRateSegment], maxHeartRate: Int, minHeartRate: Int) {
self.dateInterval = dateInterval
self.segments = segments
self.maxHeartRate = CGFloat(maxHeartRate)
self.minHeartRate = CGFloat(minHeartRate)
}
var body: some View {
Chart {
ForEach(segments, id: \.startDate) { segment in
let yStart: CGFloat = CGFloat(segment.samples.min()?.heartRate ?? 0)
let yEnd: CGFloat = CGFloat(segment.samples.max()?.heartRate ?? 0)
RuleMark(x: .value("startDate", segment.startDate), yStart: yStart, yEnd: yEnd)
}
}
.chartYScale(domain: [minHeartRate, maxHeartRate], range: minHeartRate...maxHeartRate)
.aspectRatio(contentMode: .fit)
.chartYAxis {
AxisMarks(values: [minHeartRate, maxHeartRate])
}
.chartXScale(domain: [dateInterval.start, dateInterval.end])
.chartPlotStyle { content in
content.frame(height: 200)
}
.padding()
}
}
If I understand the problem correctly, the values and labels are not plotting with the minimum(on bottom) to maximum(on top). Try changing the values to plottable values. This should fix the values, labels and the domain range.
Use PlottableValue's .value for yStart and yEnd in the RuleMark, like this:
RuleMark(
x: .value("startDate", segment.startDate),
yStart: .value("min", yStart),
yEnd: .value("max", yEnd))
The result:
These are the heart rate ranges for the first 5 marks of the chart shown:
I'm pasting my complete test based on your example because I made some minor changes for syntax reasons and made some assumptions about how the HeartRateSegment was structured. Hopefully this offers some help if you haven't resolved it already.
import SwiftUI
import Charts
struct ContentView: View {
var body: some View {
HeartRateChart(dateInterval: DateInterval(start: .now, end: Calendar.current.date(byAdding: .minute, value: 60, to: .now) ?? .now), segments: heartRateSegments, maxHeartRate: 132, minHeartRate: 77)
}
}
struct HeartRateChart: View {
let dateInterval: DateInterval
let segments: [HeartRateSegment]
let maxHeartRate: Int
let minHeartRate: Int
init(dateInterval: DateInterval, segments: [HeartRateSegment], maxHeartRate: Int, minHeartRate: Int) {
self.dateInterval = dateInterval
self.segments = segments
self.maxHeartRate = maxHeartRate
self.minHeartRate = minHeartRate
}
var body: some View {
Chart {
ForEach(segments, id: \.startDate) { segment in
let yStart = segment.heartRate.min() ?? 0
let yEnd = segment.heartRate.max() ?? 0
RuleMark(x: .value("startDate", segment.startDate),
yStart: .value("min", yStart),
yEnd: .value("max", yEnd))
}
}
.chartYScale(domain: [minHeartRate, maxHeartRate])
.aspectRatio(contentMode: .fit)
.chartYAxis {
AxisMarks(values: [minHeartRate, maxHeartRate])
}
.chartXScale(domain: [dateInterval.start, dateInterval.end])
.chartPlotStyle { content in
content.frame(height: 100)
}
.padding()
}
}
struct HeartRateSegment: Identifiable {
let id: UUID = UUID()
let startDate: Date
let heartRate: [Int]
}
// Randomly creates some heartrate data. Each segment represents a minute.
let heartRateSegments: [HeartRateSegment] = {
var segments: [HeartRateSegment] = []
for j in stride(from: 0, to: 3600, by: 60) { // every minute for 1 hour (in seconds)
let minheartRate = Int.random(in: 77...107)
let span = Int.random(in: 5...25)
let heartRates = [minheartRate, minheartRate + span]
print(heartRates)
segments.append( HeartRateSegment(startDate: Date().addingTimeInterval(TimeInterval(j)), heartRate: heartRates))
}
return segments
}()