I have a datasource that counts the number of occurrences of location data within certain bins of time. What I want to do is display a chart that shows the count of locations recorded in each of my "bins". The bin size can be 1 hour, 1 day, 1 month, 1 year.
I prepare my data in a struct as follows. This struct can generate the necessary interval dates between the start and end.
Problem Code
struct DateRange {
let startDate: Date
let endDate: Date
func datesForIntervalWith(step magnitude: Int, on component: Calendar.Component) -> [Date] {
return self.startDate.datesForIntervalWith(date: self.endDate, step: magnitude, on: component)
}
}
The data is then shared by a ViewModel as such:
class InformationViewModel: ObservableObject {
@Published var displayRange: ChartDisplayRange = .day
@Published var chartData: [DisplayData] = []
/// other code
}
I expect this data to then be displayed by a chart using the following code:
var body: some View {
Picker("Display Date Range", selection: $informationVM.displayRange) {
ForEach(ChartDisplayRange.allOptions(), id: \.self){ option in
Text(option.rawValue)
}
}
.pickerStyle(.segmented)
.onChange(of: informationVM.displayRange) { range in
informationVM.updateChartData()
}
Chart {
ForEach(informationVM.chartData) { data in
BarMark(
x: .value("Date", data.date, unit: informationVM.displayUnit),
y: .value("Tiles", data.count)
)
.foregroundStyle(.purple.gradient)
}
}
.frame(maxHeight: 200.0)
.chartXAxis {
AxisMarks(values: informationVM.chartData.map { $0.date }) { dateMark in
switch informationVM.displayRange {
case .day:
AxisValueLabel(format: Date.FormatStyle().hour().day(), centered: true, collisionResolution: .greedy)
case .week:
AxisValueLabel(format: Date.FormatStyle().day(), centered: true, collisionResolution: .greedy)
case .month:
AxisValueLabel(format: Date.FormatStyle().day(), centered: true, collisionResolution: .greedy)
case .year:
AxisValueLabel(format: Date.FormatStyle().month(.narrow), centered: true, collisionResolution: .greedy)
default:
AxisValueLabel(format: Date.FormatStyle().month(.narrow).year(), centered: true, collisionResolution: .greedy)
}
}
}
.padding()
}
Problem Example
The axis labels change based on the viewing range selected by the picker. This particular feature works. The problem is in the alignment of the actual axis labels and the data being displayed. In the following pictures we see this miss-match in action. In the console I print out the individual date bins and their associated values and show the resulting chart. As you can see the data starts on 4-11 00:00
and ends on 4-12 00:00
. With some data points collected at the 4-11 21:00
mark. However, the actual ticks on the x-axis are totally off. They don't match up with the data at all and this is true regardless of the Picker
setting.
I have tried manually setting the labels using index % x == 0
but that does not work either. I have also tried changing the centered
property and the collisionResolution
. The latter set to .automatic
results in 24 overlapping and unreadable labels.
Any idea what is wrong here?