briancl briancl - 5 months ago 63
Swift Question

How do I create an x-axis with Swift Charts with 1 unit per day instead of 1 unit per data point?

I have a chart that takes time series data. The time series data is recorded at most once per day, but on an irregular schedule. I.e., the data could look like this:

1/1, 1500
1/13, 1600
1/14, 1700
2/22, 1800
5/1, 1400


Using Charts, my line graph populates the x-axis with one unit per data point (i.e., in the example above, there would be 5 units on the xaxis, instead of ~120 days)

How do I create the x-axis with units for all dates in range?

The code for my chart:

func setChartData(data: ([String], [Double])) {
var yVals1 = [ChartDataEntry]()
for i in 0 ..< data.0.count {
yVals1.append(ChartDataEntry(value: data.1[i], xIndex: i))
}

let set1: LineChartDataSet = LineChartDataSet(yVals: yVals1, label: nil)
set1.axisDependency = .Left

set1.setColor(UIColor(red:0.502, green:0.580, blue:0.784, alpha:1.000))
set1.lineWidth = 2.0
set1.fillAlpha = 1
set1.fillColor = UIColor(red:0.502, green:0.580, blue:0.784, alpha:1.000)
set1.highlightColor = UIColor.whiteColor()
set1.drawValuesEnabled = false
set1.drawCirclesEnabled = false
set1.drawFilledEnabled = true


var dataSets : [LineChartDataSet] = [LineChartDataSet]()
dataSets.append(set1)

let data: LineChartData = LineChartData(xVals: data.0, dataSets: dataSets)
data.setValueTextColor(UIColor.whiteColor())

let legend = lineChartView.legend
legend.enabled = false

let xAxis = lineChartView.xAxis
xAxis.drawGridLinesEnabled = false
xAxis.drawAxisLineEnabled = false
xAxis.labelPosition = .Bottom

let rightAxis = lineChartView.rightAxis
rightAxis.drawAxisLineEnabled = false
rightAxis.drawLabelsEnabled = false
rightAxis.drawGridLinesEnabled = false

let leftAxis = lineChartView.leftAxis
leftAxis.drawAxisLineEnabled = false
leftAxis.gridColor = UIColor.blackColor().colorWithAlphaComponent(0.1)
leftAxis.gridLineWidth = 2


self.lineChartView.data = data
}


Here is a screenshot of the graph in my app.

Answer

Instead of passing in the datapoints directly, transform the datapoints to include all relevant days as x-values (1 day intervals, even if there is no associated y-value for that day) and appropriately fill in the y-values where they exist.

  1. Build an array of x-values that contains 1 element per day
  2. If that day has a value, then add the value to the y-value array. Otherwise add nil to the y-value array.

Code:

struct StatsViewModel {

    var exercise: Exercise

    var workoutDates: ([String], [Double?]) {
        if let lastWorkout = exercise.workoutDiary.last, firstWorkout = exercise.workoutDiary.first {
            var dates = [String]()
            var stats = [Double?]()
            let ti:NSTimeInterval = 24*60*60 //one day
            let dateFrom = firstWorkout.date
            let dateTo = lastWorkout.date

            var nextDate =  dateFrom
            let endDate = dateTo.dateByAddingTimeInterval(ti)

            while nextDate.compare(endDate) == NSComparisonResult.OrderedAscending
            {
                dates.append(nextDate.myPrettyString)
                let workout = exercise.workoutDiary.filter { $0.date.myPrettyString == nextDate.myPrettyString }.first
                if let workout = workout {
                    stats.append(Double(workout.totalVolume))
                } else {
                    stats.append(nil)
                }
                nextDate = nextDate.dateByAddingTimeInterval(ti)
            }           
            return (dates, stats)
        }
        return ([], [])
    }
}

Create the ChartDataEntry from the data points generated above, accounting for nil:

var statsViewModel: StatsViewModel!

override func viewDidLoad() {
    setChartData(statsViewModel.workoutDates)
}

func setChartData(data: ([String], [Double?])) {
    var yVals1 = [ChartDataEntry]()
    for i in 0 ..< data.0.count {
        if let yval = data.1[i] {
            yVals1.append(ChartDataEntry(value: yval, xIndex: i))
        }
    }
    let set1: LineChartDataSet = LineChartDataSet(yVals: yVals1, label: nil)
    var dataSets : [LineChartDataSet] = [LineChartDataSet]()
    dataSets.append(set1)

    let data: LineChartData = LineChartData(xVals: data.0, dataSets: dataSets)
    data.setValueTextColor(UIColor.whiteColor())
    ...
}