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.
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)
}
}
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.
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. BarMark
s 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.
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)
}
})