Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI Chart y-axis data does not align properly when using dates

I created a Line Chart in SwiftUI.

Drawing a Chart wasn't a problem. But I don't know why data doesn't align with y-axis label and grid line. The data is plotted slightly below.

enter image description here

It's just basic code. So I don't have anything to try some.

Is just SwiftUI Bug?

Does anyone know about this issue?

This is the whole code

import SwiftUI
import Charts

struct Time {
    let date: String
    let time: String
}

struct ChartsView: View {
    
    @State var timeData: [Time] = [
        .init(date: "2023-12-01", time: "07:50:00"),
        .init(date: "2023-12-02", time: "08:00:00"),
        .init(date: "2023-12-03", time: "08:10:00"),
        .init(date: "2023-12-04", time: "08:00:00"),
        .init(date: "2023-12-05", time: "08:00:00"),
        .init(date: "2023-12-06", time: "07:50:00"),
        .init(date: "2023-12-07", time: "08:00:00"),
        .init(date: "2023-12-08", time: "07:50:00"),
        .init(date: "2023-12-09", time: "08:10:00"),
        .init(date: "2023-12-10", time: "08:10:00"),
        .init(date: "2023-12-11", time: "08:10:00"),
        .init(date: "2023-12-12", time: "08:10:00"),
        .init(date: "2023-12-13", time: "07:50:00"),
        .init(date: "2023-12-14", time: "08:10:00"),
        .init(date: "2023-12-15", time: "07:50:00"),
        .init(date: "2023-12-16", time: "07:50:00"),
        .init(date: "2023-12-17", time: "08:00:00"),
        .init(date: "2023-12-18", time: "08:00:00"),
        .init(date: "2023-12-19", time: "08:00:00"),
        .init(date: "2023-12-20", time: "08:00:00"),
        .init(date: "2023-12-21", time: "08:00:00"),
        .init(date: "2023-12-22", time: "08:10:00"),
        .init(date: "2023-12-23", time: "08:00:00"),
        .init(date: "2023-12-24", time: "08:00:00"),
        .init(date: "2023-12-25", time: "08:00:00"),
        .init(date: "2023-12-26", time: "07:50:00"),
        .init(date: "2023-12-27", time: "08:00:00"),
        .init(date: "2023-12-28", time: "08:00:00"),
        .init(date: "2023-12-29", time: "08:00:00"),
        .init(date: "2023-12-30", time: "08:10:00"),
        .init(date: "2023-12-31", time: "07:50:00")
    ]
    
    var body: some View {
        ZStack(alignment: .bottom, content: {
            Chart {
                ForEach(timeData, id: \.date) {
                    LineMark(x: .value("x", $0.date.dateYYYYMMdd ?? Date(), unit: .day), 
                             y: .value("y", $0.time.dateHHmmss ?? Date(), unit: .minute))
                        .foregroundStyle(.red.opacity(0.3))
                        .lineStyle(StrokeStyle(lineWidth: 1))
                        .symbol {
                            Circle()
                                .fill(.red)
                                .frame(width: 4)
                        }
                    RuleMark(y: .value("y", "08:08:00".dateHHmmss ?? Date(), unit: .minute))
                }
            }
            .frame(height: 165)
            .padding(24)
        })        
    }
}

extension String {
    var dateYYYYMMdd: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "YYYY-MM-dd"
        return formatter.date(from: self)
    }
    
    var dateHHmmss: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        return formatter.date(from: self)
    }
}
like image 628
Justin.Shim Avatar asked Sep 02 '26 01:09

Justin.Shim


1 Answers

You should remove unit: .minute from the y axis' value.

The unit: plots the value in the half way point between two consecutive units that you specify. For example, unit: .minute would put 08:10 at 08:10:30, because that is the half way point between 08:10 and 08:11.

enter image description here

This is produced by 3 rule marks:

RuleMark(y: .value("y", "08:08:00".dateHHmmss ?? Date()))
    .foregroundStyle(.red)
RuleMark(y: .value("y", "08:08:00".dateHHmmss ?? Date(), unit: .minute))
    .foregroundStyle(.green)
RuleMark(y: .value("y", "08:09:00".dateHHmmss ?? Date()))
    .foregroundStyle(.blue)

While I can't think of a good reason to use unit: in line graphs, it is very useful when doing bar charts. BarMarks determine how wide the bar should be, then data is plotted in the centre point of the bar. If you combine this with axis labels centred between the grid lines of each unit, the axis labels would be at the bottom centres of the bars.

enter image description here

Code:

Chart {
    ForEach(data, id: \.date) {
        BarMark(x: .value("x", $0.date, unit: .day),
                y: .value("y", $0.y))
            .foregroundStyle(.red.opacity(0.3))
    }
}
.chartXAxis(content: {
    AxisMarks(values: AxisMarkValues.stride(by: .day)) { _ in
        AxisGridLine()
        AxisValueLabel(format: .dateTime.month().day(), centered: true)
    }
})
like image 170
Sweeper Avatar answered Sep 04 '26 15:09

Sweeper



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!