Search code examples
iosswiftios-charts

How to add unrelated array to Datapoint labels in an iOS-charts line chart?


I have read How to add Strings on X Axis in iOS-charts? but it does not answer my question.

I have rendered a line chart with iOS-charts with no x/y axis labels, only datapoint labels, like this. This chart shows the temperatureValues in the chart's ChartDataEntry as the Y at intervals of 0..

var temperaturevalues: [Double] = [47.0, 48.0, 49.0, 50.0, 51.0, 50.0, 49.0]

However, I want to add an unrelated array of times to the datapoint labels, with times[i] matching temperatureValues at i.

var times: [String] = ["7 AM", "8 AM", "11 AM", "2 PM", "5 PM", "8 PM", "11 PM"]

I have extended IValueFormatter in a separate class like this:

class ChartsFormatterToDegrees: IValueFormatter {    
func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
    return "\(Int(value))°"
}

but it only allows me to customize the dataPoint label using the value in the ChartDataEntry which is the temperatureValue being used to map out Y in the lineChart. How can I add another array at indexValue so that the dataPoints labels appear like this instead?

    return """
    \(Int(value))°
    \(timesOfDay)
    """

Here is my charts method:

private func setupChartView(temperatureValues: [Double], timesDataPoints: [String]) {
    var tempDataEntries: [ChartDataEntry] = []
    let chartsFormatterToDegrees = ChartsFormatterToDegrees(time: timeDataPoints)


    for eachTemp in 0..<temperatureValues.count {
        let tempEntry = ChartDataEntry(x: Double(eachTemp), y: temperatureValues[eachTemp])
        tempDataEntries.append(tempEntry)
    }

    let lineChartDataSet = LineChartDataSet(entries: tempDataEntries, label: "nil")
    lineChartDataSet.valueFormatter = chartsFormatterToDegrees

    let chartData = LineChartData(dataSets: [lineChartDataSet])
    chartData.addDataSet(lineChartDataSet)
    lineChartView.data = chartData
}

As I best understand it, the IValueFormatter extension only lets you modify the values being used to draw the chart, not add additional string arrays at index. When I tried the below, it only prints the timesOfDay at the dataSetIndex, it only prints timesOfDay[0] on all the dataPoint labels.

class ChartsFormatterToDegrees: IValueFormatter {
init(time: [String]) {
    timesOfDay = time
}
var timesOfDay = [String]()

func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
    return """
    \(Int(value))°
    \(timesOfDay[dataSetIndex])
    """
}
}

Here's my most updated method with print:

class ChartsFormatterToDegrees: IValueFormatter {

     var timesOfDay = [String]()
     var values = [Double]()

     init(values: [Double], time: [String]) {
         timesOfDay = time
         //print(values, "are the values") //prints values correctly
     }

     func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
        //print(value, "at", dataSetIndex) //prints as 37.0 at 0, 39.0 at 0, 40.0 at 0 etc

          if let index = values.firstIndex(of: value) {
              return "\(Int(value))° \(timesOfDay[index])"
          }

        let i = values.firstIndex(of: value)
        //print(i as Any, "is index of where value shows up") //prints as nil is index of where value shows up for each i

          return "\(Int(value))°"
      }
    }

Solution

  • Your assumption is not right. In IValueFormatter, you can construct any string for the corresponding value. The reason you are seeing only value or timeOfDay is because you are constructing String with """ notation that adds multiple lines whereas the label used for entry point may not calculate its size correctly because of this. You should create the String as below and see what you have got

    class ChartsFormatterToDegrees: IValueFormatter {
    
         var timesOfDay = [String]()
         var values = [Double]()  
    
         init(values: [Double], time: [String]) {
             timesOfDay = time
             values = values
         }
    
         func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
              if let index = values.firstIndex(of: value) {
                  return "\(Int(value))° \(timesOfDay[index])"
              }
              return "\(Int(value))°"
          }
    }