5

I'm trying to make my datapoint labels in a linechart display a custom string instead of their actual number (using the iOS Charts/ Charts library). I want to know if there is something like IAxisFormatter which I used to format my x and y axis labels.

I was wondering if anyone knows how exactly to do that in Swift? I can't seem to find any examples online. Thanks!

holycamolie
  • 277
  • 1
  • 3
  • 10

5 Answers5

8

You have to append IValueFormatter protocol to your ViewController and implement stringForValue(_:entry:dataSetIndex:viewPortHandler:) method (1).

Then set ViewController as valueFormatter delegate for charts data set (2).

import UIKit
import Charts

class ViewController: UIViewController, IValueFormatter {

    @IBOutlet weak var lineChartView: LineChartView!

    // Some data
    let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
    let unitsSold = [20.0, 4.0, 3.0, 6.0, 12.0, 16.0, 4.0, 18.0, 2.0, 4.0, 5.0, 4.0]

    // (1) Implementation the delegate method for changing data point labels. 
    func stringForValue(_ value: Double,
                        entry: ChartDataEntry,
                        dataSetIndex: Int,
                        implement delegate methodviewPortHandler: ViewPortHandler?) -> String{

        return "My cool label " + String(value)
    }

    func setChart(dataPoints: [String], values: [Double]){
        var dataEntries: [ChartDataEntry] = []

        // Prepare data for chart
        for i in 0..<dataPoints.count {
            let dataEntry = ChartDataEntry(x: Double(i), y: values[i])
            dataEntries.append(dataEntry)
        }

        let lineChartDataSet = LineChartDataSet(values: dataEntries, label: "Units Sold")
        let lineChartData = LineChartData(dataSets: [lineChartDataSet])

        // (2) Set delegate for formatting datapoint labels
        lineChartData.dataSets[0].valueFormatter = self

        lineChartView.data = lineChartData
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        setChart(dataPoints: months, values: unitsSold)
    }
}
AlexSmet
  • 2,141
  • 1
  • 13
  • 18
  • Yeah I used a similar method to that to get my axis labels. I think I might have been unclear in my question; I'm trying to find how to edit the actual labels on the data points themselves, if that's possible. Thanks for your answer though! – holycamolie Apr 05 '17 at 22:57
  • 1
    @holycamolie, I see. I have changed my answer. You need to implement IValueFormatter protocol with stringForValue(_:entry:dataSetIndex:viewPortHandler:) method. And set it as valueFormatter delegate for chart data set. – AlexSmet Apr 06 '17 at 08:03
4

in my case my data set has values of y, x is index

enter image description here

// this shows date string instead of index
let dates = ["11/01", "11/02", "11/03", "11/04"...etcs]
chartView.xAxis.valueFormatter = IndexAxisValueFormatter(values:months)

after set axis values enter image description here

stan liu
  • 1,712
  • 1
  • 11
  • 14
  • Please add some context to your answer. Code-only answers are not seen as "high-quality-posts" on SO. Just take the time to write 1-2 sentences as to why and how this solves the problem – Neuron Nov 15 '17 at 08:36
  • If I put too much code it seems will complicate the answer. So I think keep the answer right here is simpler. – stan liu Nov 18 '17 at 05:05
  • add context, NOT code. explain your code with words – Neuron Nov 20 '17 at 06:43
2

Memory leak alert!

In response to this answer:

You have to append IValueFormatter protocol to your ViewController and implement stringForValue(_:entry:dataSetIndex:viewPortHandler:) method (1).

Then set ViewController as valueFormatter delegate for charts data set (2).

I found the answer very helpful. It helped me get my program working. But it caused a memory leak. The view controller has a strong reference to the graph object, which gets a strong reference back to the view controller thanks to the value formatter delegate:

        // (2) Set delegate for formatting datapoint labels
    lineChartData.dataSets[0].valueFormatter = self

Here's a graph of my memory consumption in my program thanks to that leak as I instantiate and exit my (running on MacOS) view controller 3 times

To solve the memory leak I put my 3 date formatters in a separate "ChartsFormatter" file:

class ChartsFormatterBlank: IValueFormatter {
func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
    // We do not want labels on each data point in our graph
    return ""
}
}
class ChartsFormatterPercent: IAxisValueFormatter {
    func stringForValue(_ value: Double, axis: AxisBase?) -> String {
        // y-value percent
        return "\(Int(value*100))%"
    }
}
class ChartsFormatterDateShort: IAxisValueFormatter {
    func stringForValue(_ value: Double, axis: AxisBase?) -> String {
        let date = Date(timeIntervalSinceReferenceDate: value)
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .short
        dateFormatter.timeStyle = .short
        return dateFormatter.string(from:date)
    }
}

And here's how I called them in my view controller:

let chartsFormatterBlank = ChartsFormatterBlank()
let chartsFormatterPercent = ChartsFormatterPercent()
let chartsFormatterDateShort = ChartsFormatterDateShort()

line1.valueFormatter = chartsFormatterBlank

lineChart.xAxis.valueFormatter = chartsFormatterDateShort
lineChart.leftAxis.valueFormatter = chartsFormatterPercent
lineChart.rightAxis.valueFormatter = chartsFormatterPercent

I would rather set a property to not have labels on the datapoints, as opposed to my current system of creating an empty string for each datapoint. But I haven't figured out how to do that.

Darrell Root
  • 724
  • 8
  • 19
  • Even with the above actions, further work with instruments shows (smaller) memory leaks which I believe are within the charts library. – Darrell Root Feb 14 '19 at 20:37
0

In setup:

self.lineChartView.xAxis.valueFormatter = self

and after it:

extension YourViewController: ChartViewDelegate, IValueFormatter, IAxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
    let formatter = DateFormatter()
    formatter.dateFormat = "dd.MM"
    return formatter.string(from: Date(timeIntervalSince1970: value))
}

}

Result: result

0

A little bit late, but in case someone has the same problem, I believe that this question refers to putting a string inside the datapoint marker within the chart.

Each entry in a linechart is a ChartDataEntry, which by default initializes with Doubles for x and y. You can set any object to that entry with setValue:

entry.setValue(value: AnyObject, forKey: "data")

That would allow you to use that object in the setLabel() method of the marker class you use. For example:

class BalloonMarker: Marker
{

        ...
    open override func refreshContent(entry: ChartDataEntry, highlight: Highlight)
    {
        setLabel("\((entry.data as! AnyObject).date) \(String(entry.y))")
    }
}

In the example above I prefix the datapoint value with a string date, which is a property of the object I set using setValue() method before.

Hope this helps.

fconelli
  • 161
  • 4