Search code examples
swiftuiswiftui-charts

Formatting X axis labels in BarMarks using Swift Charts


I am trying to format the x axis labels using the AxisMarks(values: .automatic) modifier which is not centering the labels. I tried using stride(by: .year) which works but the year values are truncated which is expected due to the number of bars on the x-axis. Is there a better way using AxisMarks(values: .automatic)/or other means to center the labels on the x-axis. Below is my code. Any help/direction is very much appreciated.

struct ContentView: View {
    let dateValues: [DateValue] = [
//        DateValue(date: Calendar.current.date(from: DateComponents(year: 2010, month: 12, day: 31))!, value: 1250),
//        DateValue(date: Calendar.current.date(from: DateComponents(year: 2011, month: 12, day: 31))!, value: 1600),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2012, month: 12, day: 31))!, value: 1750),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2013, month: 12, day: 31))!, value: 1900),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2014, month: 12, day: 31))!, value: 1800),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2015, month: 12, day: 31))!, value: 1700),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2016, month: 12, day: 31))!, value: 2000),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2017, month: 12, day: 31))!, value: 2100),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2018, month: 12, day: 31))!, value: 2000),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2019, month: 12, day: 31))!, value: 2250),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2020, month: 12, day: 31))!, value: 2225),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2021, month: 12, day: 31))!, value: 2300),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2022, month: 12, day: 31))!, value: 2500),
        DateValue(date: Calendar.current.date(from: DateComponents(year: 2023, month: 12, day: 31))!, value: 2600),
    ]
    var body: some View {
        Chart {
            ForEach(dateValues, id:\.date) { point in
                BarMark(
                    x: .value("Date", point.date, unit: .year),
                    y: .value("Value", point.value))
                .annotation(alignment: .center) {
                    Text(abs(point.value) >= 1e-03 ? String(format: "%.1f", point.value) : "")
                        .font(.caption2)
                        .rotationEffect(.degrees(-90))
                        .frame(width: 70)
                }
                .cornerRadius(5)
            }
        }
        .chartXAxis {
            AxisMarks(values: .automatic/*.stride(by: .year)*/) { year in
                AxisTick()
                AxisGridLine()
                AxisValueLabel(format: .dateTime.year(), centered: true)
            }
        }
    }
}

struct DateValue {
    var date: Date
    var value: Double
}

Solution

  • You can use collisionResolution for the axis labels, e.g. like this:

    AxisValueLabel(format: .dateTime.year(), centered: true, collisionResolution: .greedy)