2

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))°"
      }
    }
Kamran
  • 14,987
  • 4
  • 33
  • 51
bobcat
  • 177
  • 1
  • 12

1 Answers1

2

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))°"
      }
}
Kamran
  • 14,987
  • 4
  • 33
  • 51
  • This returns only timesOfDay[0] at all of the dataPoint labels. The issue isn't that I'm only seeing either value or timeOfDay, but that the timeOfDay isn't showing up accurately at each index. The label point seems to have no trouble calculating its size correctly because I'm able to get the value to print twice at each label point, [like this](https://imgur.com/a/IKaiHln). – bobcat Oct 30 '19 at 18:58
  • I just edited my question post and added my full IValueFormatter method at the bottom. – bobcat Oct 30 '19 at 19:05
  • Thank you for the updated answer but I'm still not getting timesOfDay showing at the index. When I print(value, "at", dataSetIndex), it prints each of the values at index 0, like 37.0 at 0, 39.0 at 0, 40.0 at 0 etc. I can print(values) in the init() and these print correctly as an array. If I print(index) from 'let index = values.firstIndex(of: value) as Any', this prints as "nil is index of where value shows up". I really don't understand why this isn't working in an obvious way. I have added my full method with the prints to my original post. – bobcat Nov 01 '19 at 03:57
  • @bhealth My bad. I just forgot to assign `values` in the `init` that is why values array is always empty so index was `nil`. Please check the updated answer. Sorry for the confusion. – Kamran Nov 01 '19 at 08:04
  • This did it. Thank you! – bobcat Nov 01 '19 at 16:52