I'm using the iOS Charts and I'm getting a repeating labels on my x-axis.
I'm getting numerous answers (first, second, third) that I should set the granularity settings:
xAxis.granularityEnabled = true
xAxis.granularity = 1.0
but, when I do the labels on the x-axis simply disappear.
My settings for the x-axis:
let xAxis = lineChartView.xAxis
xAxis.labelPosition = .bottom
xAxis.labelFont = .boldSystemFont(ofSize: 12)
xAxis.setLabelCount(6, force: false)
xAxis.labelTextColor = UIColor(red: 0, green: 0, blue: 255/255, alpha: 1.0)
xAxis.axisLineColor = UIColor(white: 0.2, alpha: 0.4)
xAxis.gridColor = UIColor(white: 0.8, alpha: 0.4)
xAxis.valueFormatter = ChartXAxisFormatter()
I've created a custom class to format the values being passed to the x-axis on the advise of the documentation:
class ChartXAxisFormatter: IAxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("MM/dd")
let date = Date(timeIntervalSince1970: value)
return dateFormatter.string(from: date)
}
}
Update
Thanks to @aiwiguna and with the help from the Charts repo, I was able to create a custom class that takes in two things: a date (Double) and a corresponding index (Int), instead of just the date.
class ChartXAxisFormatter: NSObject, IAxisValueFormatter {
private var dates: [Double]?
convenience init(usingDates dateArr: [Double]) {
self.init()
self.dates = dateArr
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
print("value: \(value)")
print("dates: \(dates)")
let index = Int(value)
guard let dates = dates, index < dates.count else {
return "?"
}
let date = dates[index]
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("MM/dd")
let formattedDate = Date(timeIntervalSince1970: date.rounded())
return dateFormatter.string(from: formattedDate)
}
}
The idea is to have an array of dates, [1598409060.0, 1598409060.0]
, and returning them one by one by accessing it with an index. Logging the value
and index
does show this to be the case.
The problem is, while the number of repeating labels have been reduced to match the number of plots, the duplicates still remain:
There two plots should both be stacked on top of each other on one x-axis label "8/25".
I'm using enumerated()
to extract the index for ChartDataEntry
:
var dateArr = [Double]()
for (index, fetchedMetric) in fetchedMetrics.enumerated() {
let date = fetchedMetric.date.timeIntervalSince1970
dateArr.append(date)
if var yValues = metricDict[fetchedMetric.unit] {
yValues.append(ChartDataEntry(x: Double(index), y: Double(truncating: fetchedMetric.value)))
metricDict.updateValue(yValues, forKey: fetchedMetric.unit)
} else {
metricDict.updateValue([ChartDataEntry(x: Double(index), y: Double(truncating: fetchedMetric.value))], forKey: fetchedMetric.unit)
}
}
it because when you set your chart setup (granurality = 1 and ChartXAxisFormatter) is showing each second in xAxis
I prefer to use array date to show day in axis, with something like this
public class DateValueFormatter: NSObject, IAxisValueFormatter {
private let dateFormatter = DateFormatter()
private let dates:[Date]
init(dates: [Date]) {
self.dates= dates
super.init()
dateFormatter.dateFormat = "dd MMM HH:mm"
}
public func stringForValue(_ value: Double, axis: AxisBase?) -> String {
if value >= 0 && value < objects.count{
let date = dates[Int(value)]
return dateFormatter.string(from: date)
}
return ""
}
}
if you don't want to change your value formatted you can try to set granularity to 86400 (total seconds in 1 day)