[quote='794978022, AppleCare Staff, /thread/759244?answerId=794978022#794978022']
No need for an entire repo, just a minimal, but runnable example that exhibits the issue.
[/quote]
Alright! This should do:
import SwiftUI
import Charts
struct ChartWithoutNilValues: View {
let firstDayOfMonth: Date
let firstDayOfNextMonth: Date
let measurements: [PerformanceMeasurement]
init() {
firstDayOfMonth = Date.now.firstDayOfMonth()!
firstDayOfNextMonth = Calendar.current.date(byAdding: .month, value: 1, to: firstDayOfMonth)!
measurements = DataGenerator.generateMeasurements(
interval: DateInterval(start: firstDayOfMonth, end: firstDayOfNextMonth),
component: .day
)
}
var body: some View {
Chart(measurements, id: \.timestamp) { measurement in
BarMark(
x: .value(
"Timestamp",
measurement.timestamp,
unit: .weekOfYear,
calendar: .current
),
y: .value(
"Solar production",
measurement.production?.total ?? 0
)
)
}
.chartXAxis {
AxisMarks(values: .stride(by: .weekOfYear)) { value in
AxisValueLabel("\(value.index)", centered: true)
}
}
.padding()
}
}
struct ChartWithNilValues: View {
let firstDayOfMonth: Date
let firstDayOfNextMonth: Date
let measurements: [PerformanceMeasurement]
init() {
firstDayOfMonth = Date.now.firstDayOfMonth()!
firstDayOfNextMonth = Calendar.current.date(byAdding: .month, value: 1, to: firstDayOfMonth)!
measurements = DataGenerator.generateMeasurements(
interval: DateInterval(start: firstDayOfMonth, end: firstDayOfNextMonth),
component: .day
)
}
var body: some View {
Chart(measurements, id: \.timestamp) { measurement in
if let total = measurement.production?.total {
BarMark(
x: .value(
"Timestamp",
measurement.timestamp,
unit: .weekOfYear,
calendar: .current
),
y: .value(
"Solar production",
total
)
)
}
}
.chartXAxis {
AxisMarks(values: .stride(by: .weekOfYear)) { value in
AxisValueLabel("\(value.index)", centered: true)
}
}
.padding()
.chartXScale(domain: firstDayOfMonth...firstDayOfNextMonth)
}
}
#Preview {
VStack {
ChartWithoutNilValues()
ChartWithNilValues()
}
.padding()
}
struct DataGenerator {
let currentYear = Calendar.current.component(.year, from: .now)
let startOfDay = Calendar.current.startOfDay(for: .now)
static func generateMeasurements(interval: DateInterval, component: Calendar.Component) -> [PerformanceMeasurement] {
var measurements: [PerformanceMeasurement] = []
let calendar = Calendar.current
let dateComponents = calendar.dateComponents([component], from: interval.start, to: interval.end)
guard let count = dateComponents.value(for: component) else { return [] }
for i in 0 ..< count {
if let time = calendar.date(byAdding: component, value: i, to: interval.start), time <= interval.end {
let production = createProduction(from: i > count - 10 ? nil : randomNumber)
let measurement = PerformanceMeasurement(
timestamp: time,
production: production
)
measurements.append(measurement)
}
}
return measurements
}
static private var randomNumber: Double? {
Double.random(in: 0 ... 50)
}
static private func createProduction(from total: Double?) -> PerformanceMeasurement.Production {
guard let total else {
return PerformanceMeasurement.Production(total: nil, sold: nil, consumed: nil)
}
let sold = Double.random(in: 0 ... total)
let consumed = total - sold
return PerformanceMeasurement.Production(total: total, sold: sold, consumed: consumed)
}
}
struct PerformanceMeasurement: Codable, Equatable {
let timestamp: Date
let production: Production?
struct Production: Codable, Equatable {
let total: Double?
let sold: Double?
let consumed: Double?
}
}
extension Date {
func firstDayOfMonth(in calendar: Calendar = .current) -> Date? {
calendar.date(from: calendar.dateComponents([.year, .month], from: self))
}
}