Hi everyone, I observe similar performance issue. They performance is degraded when the x axis domain is enlarged. I add 5 bars per day, and around 150 days there is a visible jitter and tearing while dragging.
I've tried various techniques to improve it such as large xAxis and displaying events only around the scroll position, however as noted above using it lowers the performance. I've also tried prepending and apending days, without having a large x axis domain, but this makes the scroll position jump.
Here is another code sample.
import Charts
import SwiftUI
struct PerformanceBarChart: View {
struct DataPoint: Identifiable {
let id = UUID()
let date: Date
let x: Int
let yStart: Date
let yEnd: Date
let category: String
}
private var today = Date.now.startOfDay()
@State private var data: [DataPoint] = []
@State private var scrollPosition: Date = Date.now
@State private var loading = false
@State var xAxisDomain: ClosedRange<Date> = Date.now...Date.now
private let calendar = Calendar.autoupdatingCurrent
private let visibleRange: TimeInterval = 7 * 24 * 60 * 60
private let chunkSize: Int = 30
var body: some View {
VStack {
Text("X Axis Domain Length: \(data.count / 5)")
Text("Until 90 it is okay, after 150 lag is noticable.")
HStack {
Button("prepend") { generate() }
.buttonStyle(.bordered)
Text(
"Scroll jumps if not at end. It cannot be controlled. There is no value change, only visual."
)
}
.padding()
HStack {
Button("set large x domain") {
var startComponents = DateComponents()
startComponents.year =
calendar.component(.year, from: today) - 1
startComponents.month = 1
startComponents.day = 1
startComponents.hour = 0
startComponents.minute = 0
startComponents.second = 0
let start = calendar.date(from: startComponents)!
xAxisDomain = start...data.last!.date
}
.buttonStyle(.bordered)
Text(
"X Axis stats from Jan 1st last year. Even if not data is added scrolling is slow."
)
}
.padding()
Text(scrollPosition, format: .dateTime)
Chart(data) {
BarMark(
x: .value("Date", $0.date, unit: .day),
yStart: .value("Start", $0.yStart),
yEnd: .value("End", $0.yEnd)
)
.foregroundStyle(by: .value("Category", $0.category))
}
.chartScrollableAxes(.horizontal)
.chartXVisibleDomain(length: 7 * 24 * 3600)
.chartXAxis {
AxisMarks(values: .stride(by: .day)) { value in
AxisGridLine()
AxisTick()
AxisValueLabel(format: .dateTime.day().month())
}
}
.chartXScale(domain: xAxisDomain)
.chartYScale(domain: today.startOfDay()...today.endOfDay())
.chartScrollPosition(x: $scrollPosition)
.chartScrollPosition(initialX: today)
.frame(height: 450)
.padding()
.onAppear {
generate()
}
}
}
private func generate() {
let first = data.first
let date = first?.date ?? today
let x = first?.x ?? 1
var newPoints = [DataPoint]()
newPoints.reserveCapacity(3 * chunkSize)
for i in 1...chunkSize {
let date = calendar.date(byAdding: .day, value: -i, to: date)!
newPoints.append(
DataPoint(
date: date,
x: x - i,
yStart: todayAt(hour: 2, minute: 0),
yEnd: todayAt(hour: 3, minute: 0),
category: String(Int.random(in: 1...3))
)
)
newPoints.append(
DataPoint(
date: date,
x: x - i,
yStart: todayAt(hour: 4, minute: 30),
yEnd: todayAt(hour: 6, minute: 30),
category: String(Int.random(in: 1...3))
)
)
newPoints.append(
DataPoint(
date: date,
x: x - i,
yStart: todayAt(hour: 9, minute: 30),
yEnd: todayAt(hour: 10, minute: 30),
category: String(Int.random(in: 1...3))
)
)
newPoints.append(
DataPoint(
date: date,
x: x - i,
yStart: todayAt(hour: 15, minute: 30),
yEnd: todayAt(hour: 17, minute: 30),
category: String(Int.random(in: 1...3))
)
)
newPoints.append(
DataPoint(
date: date,
x: x - i,
yStart: todayAt(hour: 20, minute: 0),
yEnd: todayAt(hour: 21, minute: 30),
category: String(Int.random(in: 1...3))
)
)
}
data.insert(contentsOf: newPoints.reversed(), at: 0)
xAxisDomain = data.first!.date...data.last!.date
}
private func todayAt(hour: Int, minute: Int) -> Date {
return calendar.date(
bySettingHour: hour,
minute: minute,
second: 0,
of: today
)!
}
}
#Preview {
PerformanceBarChart()
}