I am having a hard time to migrate library Charts (from Daniel Gindi) from version 2 (Swift 2.3) to 3 (Swift 3).
Basically, I can\'t have the x labels (dates) aligne
here is the Solution of xValuesNumberFormatter
let xValuesFormatter = DateFormatter()
xValuesFormatter.dateFormat = "MM/dd"
let xValuesNumberFormatter = ChartXAxisFormatter(referenceTimeInterval: referenceTimeInterval, dateFormatter: xValuesFormatter)
xValuesNumberFormatter.dateFormatter = xValuesFormatter // e.g. "wed 26"
xAxis.valueFormatter = xValuesNumberFormatter
OK, got it!
You've got to define a reference time Interval (the "0" for the x axis). And then calculate the additional time interval for each x value.
The ChartXAxisFormatter becomes:
import Foundation
import Charts
class ChartXAxisFormatter: NSObject {
fileprivate var dateFormatter: DateFormatter?
fileprivate var referenceTimeInterval: TimeInterval?
convenience init(referenceTimeInterval: TimeInterval, dateFormatter: DateFormatter) {
self.init()
self.referenceTimeInterval = referenceTimeInterval
self.dateFormatter = dateFormatter
}
}
extension ChartXAxisFormatter: IAxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
guard let dateFormatter = dateFormatter,
let referenceTimeInterval = referenceTimeInterval
else {
return ""
}
let date = Date(timeIntervalSince1970: value * 3600 * 24 + referenceTimeInterval)
return dateFormatter.string(from: date)
}
}
And, then, when I create my data entries, it works like so:
// (objects is defined as an array of struct with date and value)
// Define the reference time interval
var referenceTimeInterval: TimeInterval = 0
if let minTimeInterval = (objects.map { $0.date.timeIntervalSince1970 }).min() {
referenceTimeInterval = minTimeInterval
}
// Define chart xValues formatter
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
formatter.locale = Locale.current
let xValuesNumberFormatter = ChartXAxisFormatter(referenceTimeInterval: referenceTimeInterval, dateFormatter: formatter)
// Define chart entries
var entries = [ChartDataEntry]()
for object in objects {
let timeInterval = object.date.timeIntervalSince1970
let xValue = (timeInterval - referenceTimeInterval) / (3600 * 24)
let yValue = object.value
let entry = ChartDataEntry(x: xValue, y: yValue)
entries.append(entry)
}
// Pass these entries and the formatter to the Chart ...
The result is much nicer (I removed cubic by the way):
If you exactly know how many labels you need in the x-axis,you can write this code to solve it.For example,If I need seven labels to appear on the x-axis,Then this should be enough.The reason is the chart library is not properly calculating the intervals between the two x-axis points and hence the plot-label mismatch.When we force the library to plot against the given number of labels,The issue seems to be gone.
let xAxis = chart.xAxis
xAxis.centerAxisLabelsEnabled = false
xAxis.setLabelCount(7, force: true) //enter the number of labels here
I solved this issue using this answer https://stackoverflow.com/a/44554613/2087608
I suspected that these offsets come from adjusting X axis value to a specific time of the day in my case
Here is my code
for i in 0..<valuesViewModel.entries.count {
let dataEntry = ChartDataEntry(x: roundDate(date: valuesViewModel.entries[i].date).timeIntervalSince1970, y: valuesViewModel.entries[i].value)
dataEntries.append(dataEntry)
}
func roundDate(date: Date) -> Date {
var comp: DateComponents = Calendar.current.dateComponents([.year, .month, .day], from: date)
comp.timeZone = TimeZone(abbreviation: "UTC")!
let truncated = Calendar.current.date(from: comp)!
return truncated
}