Swift Chart Axis ordering issue

79 views Asked by At

that tracks someone's sleep into the 4 categories awake, rem, core and deep. I have the graph almost how I want it, showing the sleep data at various times of the night (each hour), however I am lost on how I am meant to get my Y axis showing in the right order. I want it split into four, for each of the sleep stages, starting top awake, below REM, below core, at the bottom deep, 25% of the height of the graph. However, no matter what I try, it maintains the order core, awake, deep, REM. I have tried using the .chartYAxis, but it didnt seem to do anything. I can see in the documentation how to adjust this for numbers, or dates, but for what I want, I am pretty lost, especially as I get no errors, it just fails silently to order them. I thought about somehow using an Integer to represent each of the sleep types, 0 = awake, 4 = deep, and somehow correspond those to a String value, but I am not sure how to achieve that. Thanks for the help!

enum SleepType: String, Plottable, CaseIterable {
    case Awake
    case REM
    case Core
    case Deep
}

struct SingleSleep: Hashable, Identifiable {
    let id = UUID()
    let startDate: Date
    let stopDate: Date
    var sleepTime: TimeInterval = 0 //in minutes
    var sleepType: SleepType
}

struct SleepExampleView: View {
    
    var sleep = [
        SingleSleep(startDate: Date(timeIntervalSinceReferenceDate: 729208384), stopDate: Date(timeIntervalSinceReferenceDate: 729209374), sleepTime: 16.5, sleepType: .Core),
        SingleSleep(startDate: Date(timeIntervalSinceReferenceDate: 729209374), stopDate: Date(timeIntervalSinceReferenceDate: 729209404), sleepTime: 16.5, sleepType: .Awake),
        SingleSleep(startDate: Date(timeIntervalSinceReferenceDate: 729209404), stopDate: Date(timeIntervalSinceReferenceDate: 729209794), sleepTime: 16.5, sleepType: .Core),
        SingleSleep(startDate: Date(timeIntervalSinceReferenceDate: 729209794), stopDate: Date(timeIntervalSinceReferenceDate: 729211234), sleepTime: 16.5, sleepType: .Deep),
        SingleSleep(startDate: Date(timeIntervalSinceReferenceDate: 729211234), stopDate: Date(timeIntervalSinceReferenceDate: 729211864), sleepTime: 16.5, sleepType: .REM),
    ]
    
    var body: some View {
        //chart
        
        Chart {
            ForEach(sleep, id: \.self) { sleep in
                LineMark(
                    x: .value("Time", sleep.startDate),
                    y: .value("Type", sleep.sleepType.rawValue)
                )
            }
        }
        .chartYAxis {
            AxisMarks(
                values: [SleepType.Awake.rawValue, SleepType.REM.rawValue, SleepType.Core.rawValue, SleepType.Deep.rawValue]
              )
        }
        .chartXAxis {
            AxisMarks(values: .stride(by: .hour, count: 1)) { value in
                AxisValueLabel(format: .dateTime.hour(), centered: true)
            }
        }
        .frame(width: 300, height: 300)
        .preferredColorScheme(.dark)
        .background(.clear)
    }
}
1

There are 1 answers

5
Sweeper On BEST ANSWER

The order of the y values is a problem about the domain, not the axis.

You can use chartYScale to modify the domain to whatever set of values you want:

Chart {
    ForEach(sleep) { sleep in
        LineMark(
            x: .value("Time", sleep.startDate),
            y: .value("Type", sleep.sleepType)
        )
    }
}
.chartYScale(domain: .automatic(dataType: SleepType.self, modifyInferredDomain: { domain in
    // put your order here:
    domain = [SleepType.Awake, .REM, .Core, .Deep]
}))
// you don't need chartYAxis here since sleep type is discrete data
// the y axis shows all the values by default
.chartXAxis {
    AxisMarks(values: .stride(by: .hour, count: 1)) { value in
        AxisValueLabel(format: .dateTime.hour(), centered: true)
    }
}

Note that I used the SleepType values themselves to plot the y axis, instead of their rawValues. SleepType conforms to Plottable after all.