Search code examples
iosswiftswiftuichartsswiftui-charts

Swift Chart Axis ordering issue


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

Solution

  • 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.