As per chart scale modifier documentation for domain parameter:
The possible data values along the x axis in the chart. You can define the domain with a ClosedRange for number or Date values (e.g., 0 ... 500), and with an array for categorical values (e.g., ["A", "B", "C"])
It seems for date type values this function is expecting a range but since you are specifying an array the method invocation traps.
Instead of providing the domain directly, you can provide an automatic scale domain modifying the inferred domain. To set the domain to your calculated allDaytimeDates
use:
.chartXScale(domain: .automatic(dataType: Date.self) { dates in
dates = allDaytimeDates
})
Update 1
There are multiple approaches you can try to ignore night time date scale on X-axis. The simpler and not recommended approach is to provided X-axis value in your line mark as a String
instead of a Date
.
The issue with specifying X-axis value as Date
is you can only supply a range for the axis scale and you can't just pick multiple ranges as scale for your axis as of now and similarly you can't specify your scale to ignore certain range or values (i.e. night time). With specifying X-axis value as string you will be able to just ignore night time values:
LineMark(
x: .value("hour", "\(hourData.date)"),
y: .value("value", hourData.value)
)
The demerit with this approach is temprature variations as obtained from this graph is wrong as all your data points will be just separated equally regardless of their date value.
The preferred approach is to manually adjust the X-axis position for next day's data points. For your scenario you can create a DayHourWeather
type with custom X-position value:
struct DayHourWeather: Plottable {
let position: TimeInterval // Manually calculated X-axis position
let date: Date
let temp: Double
let series: String // The day this data belongs to
var primitivePlottable: TimeInterval { position }
init?(primitivePlottable: TimeInterval) { nil }
init(position: TimeInterval, date: Date, temp: Double, series: String) {
self.position = position
self.date = date
self.temp = temp
self.series = series
}
}
You can customize the position
data to move daytime plots closer together ignoring night time values. Then you can create DayHourWeather
s from your HourWeather
s:
/// assumes `hourWeathers` are filtered containing only day time data and already sorted
func getDayHourWeathers(from hourWeathers: [HourWeather]) -> [DayHourWeather] {
let padding: TimeInterval = 10000 // distance between lat day's last data point and next day's first data point
var translation: TimeInterval = 0 // The negetive translation required on X-axis for certain day
var series: Int = 0 // Current day series
var result: [DayHourWeather] = []
result.reserveCapacity(hourWeathers.count)
for (index, hourWeather) in hourWeathers.enumerated() {
defer {
result.append(
.init(
position: hourWeather.date.timeIntervalSince1970 - translation,
date: hourWeather.date,
temp: hourWeather.temp,
series: "Day \(series + 1)"
)
)
}
guard
index > 0,
case let lastWeather = hourWeathers[index - 1],
!Calendar.current.isDate(lastWeather.date, inSameDayAs: hourWeather.date)
else { continue }
// move next day graph to left occupying previous day's night scale
translation = hourWeather.date.timeIntervalSince1970 - (result.last!.position + padding)
series += 1
}
return result
}
Now to plot your chart you can use the newly created DayHourWeather
values:
var body: some View {
let dayWeathers = getDayHourWeathers(from: myDataSeperatedByHours)
Chart {
ForEach(dayWeathers, id: \.date) { hourData in
LineMark(
x: .value("hour", hourData.position), // custom X-axis position calculated
y: .value("value", hourData.temp)
)
.foregroundStyle(by: .value("Day", hourData.series))
}
}
.chartXScale(domain: dayWeathers.first!.position...dayWeathers.last!.position) // provide scale range for calculated custom X-axis positions
}
Note that with above changes your X-axis marker will display your custom X-axis positions. To change it back to the actual date label you want to display you can specify custom X-axis label:
.chartXAxis {
AxisMarks(position: .bottom, values: dayWeathers) {
AxisValueLabel(
"\(Self.shortTimeFormatter.calendar.component(.hour, from: dayWeathers[$0.index].date))"
)
}
}
The values
argument for AxisMarks
only accepts an array of Plottable
items, this is why confirming DayHourWeather
to Plottable
is needed. After above changes the chart obtained will look similar to this:
Note that I have created a different series for each day data. Although you can combine them into a single series, I will advise against doing so as the resulting chart is misleading to viewer since you are removing part of the X-axis scale.
myDataSeparateByHours
sorted? Also mightxEnd
need to usemyDataSeparateByHours.last
instead of.first
? – Claudinecategory
asScaleType
, can you try usingdate
? – AmorinoScaleType.date
I get the same error: "Fatal error: the specified scale type is incompatible with the data values and visual property." – Nb