I am having a hard time to migrate Charts from version 2 (Swift 2.3) to 3 (Swift 3).
Basically, I can't have the x labels (dates) aligned correctly with the corresponding plots.
This is what I had before in version 2:
https://i.stack.imgur.com/wOL3t.png
In version 2, I had values for days 7, 8, 10 and 11. So I was missing a day in the middle, but the labels were correctly alined with the plots.
And here is what I have in version 3:
https://i.stack.imgur.com/arIL2.png
In version 3, the "labels" in the x axis have now been replaced by double (for dates, it's a timeInterval since 1970), and formatted via a formatter. So, indeniably, the graph is more "correct" now, since the chart correctly extrapolates the value for the 9th, but I can't find how to put the labels under the corresponding plots.
This is my code for the x axis:
```
let chartView = LineChartView()
...
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
xAxis.labelCount = entries.count
xAxis.drawLabelsEnabled = true
xAxis.drawLimitLinesBehindDataEnabled = true
xAxis.avoidFirstLastClippingEnabled = true
// Set the x values date formatter
let xValuesNumberFormatter = ChartXAxisFormatter()
xValuesNumberFormatter.dateFormatter = dayNumberAndShortNameFormatter // e.g. "wed 26"
xAxis.valueFormatter = xValuesNumberFormatter
Here is the ChartXAxisFormatter class I created:
import Foundation
import Charts
class ChartXAxisFormatter: NSObject {
var dateFormatter: DateFormatter?
}
extension ChartXAxisFormatter: IAxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
if let dateFormatter = dateFormatter {
let date = Date(timeIntervalSince1970: value)
return dateFormatter.string(from: date)
}
return ""
}
}
```
So, the values here are correct, the formatting is correct, the shape of the chart is correct, but the alignment of the labels with the corresponding plots is not good.
Thanks for your help
I ran in the same issue using timestamps before and it is caused by what is being rendered on the x-axis is an interpolated timestamp. This basically means that the timestamps resulting in the labels using a dateFormatter _are not the timestamps for your scores_, causing the labels to not match up.
For example, let's assume you have x values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. The x-axis renderer might actually print 2.5 as a label trying to evenly distribute the labels on the x-axis, even though that particular x-value is _not_ in your dataset. If you then consider you are using timestamps this means the dateFormatter may format some intermediate timestamp as a date. As you format by day this results in your labels not matching up with your scores even though the label is correct (as you basically round it off by day). If you were to format by _day hour:minute_ you would see the label is indeed an interpolated date.
I solved the issue on my end by resorting to using array indexes instead of using timestamps for the x-axis and using a custom X-axis formatter:
import Foundation
import Charts
class MyCustomBottomAxisFormatter: NSObject, IAxisValueFormatter {
private var scores: [MyScoreObject]?
lazy private var dateFormatter: DateFormatter = {
// set up date formatter using locale
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.current
dateFormatter.dateStyle = .short
dateFormatter.timeStyle = .none
return dateFormatter
}()
convenience init(usingScores scores: [MyScoreObject]) {
self.init()
self.scores = scores
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let index = Int(value)
guard let scores = scores, index < scores.count, let date = scores[index].date else {
return "?"
}
return dateFormatter.string(from: date)
}
}
Thanks a lot for your answer!
And the very clear explanation about the timestamps being interpolated, it really makes sense.
I'll try your solution, and let you know.
Thanks!
@4np I guess that you then have to define your array of ChartDataEntry with xValues as indices too, instead of time intervals, right ?
Like so :
// Define chart yValues
var entries = [ChartDataEntry]()
for (index, score) in scores.enumerated() {
let yValue = score.value
let entry = ChartDataEntry(x: Double(index), y: yValue)
entries.append(entry)
}
@4np OK, that worked! My chart is now the same as in version 2.
But, I had to add 2 more things to my xAxis:
// Show the limit lines behind each plot
xAxis.drawLimitLinesBehindDataEnabled = true
// Make sure that only 1 x-label per index is shown
xAxis.granularityEnabled = true
xAxis.granularity = 1
Thanks for your help.
Also, I opened an issue at StackOverflow there:
http://stackoverflow.com/questions/41720445/ios-charts-3-0-align-x-labels-dates-with-plots
If you have a SO account and want to copy / paste your answer there, I'd be glad to accept your answer there too.
Thanks
Fred
Also, I left this issue open until the Charts devs have a chance to look at it, and maybe introduce a way to choose how the labels should be aligned.
Because I now have to choose between 2 poor solutions:
Please let us know what you think.
@frederic-adda
I managed to solve the problem
XAxis.granularity = 1.0 must be one day. that is the most important
The smallest date must be 0
(TimeInSeconds - miniDate) / (3600.0 * 24.0)

And all the trick is in the formatter
We do the opposite operation to retrieve the date
Let date2 = Date (timeIntervalSince1970: (value * 3600 * 24) + miniTime)
Return dateFormatter.string (from: date2)
do not forget
DateFormatter.timeZone = NSTimeZone (abbreviation: "GMT + 0: 00") as TimeZone!
The demo is macOS
The link is https://github.com/thierryH91200/Charts-log
Line Date tab
@thierryH91200 Thanks Thierry !
I'll try to implement your solution right away.
It worked! Fantastic!
Thanks for your help!
Cool @thierryH91200, that does indeed seem the way to go forward 👍
This does not work for me
@frederic-adda did you find the solution for your issue??
actually I have issue when showing dates on X-Axis. I am showing dates on X-Axis by setting ChartDataEntry's X-value as TimeInterval, but then chart doesn't show data properly. While setting ChartDataEntry's X-value as array index shows the data.
Here is my code for setting ChartDataEntry's X-value as TimeInterval
for (int i = 0; i < self.temperatureUpdates.count; i++)
{
SFTemperatureUpdate *temperatureUpdate = [self.temperatureUpdates objectAtIndex:i];
double yVal = [temperatureUpdate.value floatValue];
NSTimeInterval timeInterval = [temperatureUpdate.careatedAt timeIntervalSince1970];
double xVal = timeInterval;
[yVals1 addObject:[[ChartDataEntry alloc] initWithX:xVal y:yVal]];
}

Trying to get the above solutions working when using multiple LineChartDataSets on the one graph, has anyone been able to get it successfully working?
I'm having problems finding a solution when one dataset might differ in size compared to the other.
i.e
let x = LineChartDataSet(values: values1, label: "test 1") // count 5
let y = LineChartDataSet(values: values2, label: "test 2") // count 8
Most helpful comment
I ran in the same issue using timestamps before and it is caused by what is being rendered on the x-axis is an interpolated timestamp. This basically means that the timestamps resulting in the labels using a dateFormatter _are not the timestamps for your scores_, causing the labels to not match up.
For example, let's assume you have x values [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]. The x-axis renderer might actually print 2.5 as a label trying to evenly distribute the labels on the x-axis, even though that particular x-value is _not_ in your dataset. If you then consider you are using timestamps this means the dateFormatter may format some intermediate timestamp as a date. As you format by day this results in your labels not matching up with your scores even though the label is correct (as you basically round it off by day). If you were to format by _day hour:minute_ you would see the label is indeed an interpolated date.
I solved the issue on my end by resorting to using array indexes instead of using timestamps for the x-axis and using a custom X-axis formatter: