0

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?

enter image description here

enter image description here

Aleksandr
  • 533
  • 1
  • 4
  • 12
  • If you posted code that actually compiled it'd be easier to help you. I was able to get something working but without seeing what you did I can't say what you are doing wrong. I think you should try rotating the device to landscape and see what you get. – Rob C Apr 12 '23 at 06:53
  • This looks like it might be a time zone issue. Are you in a GMT+2 (or -2) time zone? Is your data source generating dates in the same time zone (i.e. using the current calendar)? – Geoff Hackworth Apr 12 '23 at 07:43
  • @GeoffHackworth Thanks, that sent me on the right track. I neglected to assume that SwiftUI will always try to convert dates when presenting in a chart so I was converting them to local TZ myself when this is unnecessary. Saved me some headache. – Aleksandr Apr 12 '23 at 15:38
  • 1
    Glad I could help a little. You probably don’t want to include the day in the labels for a daily view. Surely hour will be enough. Maybe that is just a temporary debug thing because that let me notice that the first label was for the previous day (10th, not 11th) and realise it was probably due to time zones. – Geoff Hackworth Apr 12 '23 at 15:47
  • Thanks for the advice, yeah it was debug as I couldn't figure out quite what was wrong. Appreciate your help – Aleksandr Apr 12 '23 at 16:13

0 Answers0