Instead of showing the Double values in my chart I would like to show a String for each value. I still want the chart to draw from the Double values, but I want the label to show a string with a date instead. How to do this?
I´m using Daniel Cohen Gindis library.
Here is the code for my graph:
import UIKit
import Charts
class ViewController: UIViewController {
#IBOutlet var barChartView: BarChartView!
override func viewDidLoad() {
super.viewDidLoad()
//x line values
let tools = ["Saw","Sissor","Axe"
,"Hammer","Tray"]
//y line values
let values = [1.0,2.0,3.0,4.0,5.0]
setChart(tools, values: values, colors: colors)
}
func setChart(xAxisLabels: [String], values: [Double], colors: [UIColor]){
var dataEntries : [BarChartDataEntry] = []
for i in 0..<xAxisLabels.count {
let dataEntry = BarChartDataEntry(value: values[i], xIndex: i )
dataEntries.append(dataEntry)
}
barChartView.leftAxis.axisMinValue = 0.0
barChartView.leftAxis.axisMaxValue = 14.0
barChartView.data = chartData
}
}
You can use the String() method to convert any data type into a string.
For example:
var myDouble: Double = 1.5
print(String(myDouble)) // Prints "1.5"
I don't know if you still need the answer, but this may help somebody who struggles with the same problem :)
First of all, you need to make a ValueFormatter class for your xAxis.
xAxis.valueFormatter = XAxisValueFormatter()
And then you have to put the following in your new class:
class XAxisValueFormatter: NSObject, IAxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let dateFormatterPrint = DateFormatter()
dateFormatterPrint.dateFormat = "HH:mm:ss"
let date = Date(timeIntervalSince1970: value)
let time = dateFormatterPrint.string(from: date)
return time
}
}
Related
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))°"
}
}
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))°"
}
}
I have an issue creating a string value for xAxis using the iOS Charts library
x value always has duplicate value, see following picture, you can see value always be JAN JAN JAN JAN FEB FEB FEB
How I can setup chart's x value as it will show JAN FEB MAR ?
import UIKit
import Charts
class ViewController: UIViewController {
var months:[String]!
#IBOutlet var lineChartView: LineChartView!
override func viewDidLoad() {
super.viewDidLoad()
let unitsSold = [20.0, 4.0, 6.0]
var months = ["Jan", "Feb", "Mar"]
let formato:LineChartFormatter = LineChartFormatter(labels: months)
let xaxis:XAxis = XAxis()
var dataEntries: [ChartDataEntry] = []
for i in 0..<unitsSold.count {
let dataEntry = ChartDataEntry(x: Double(i), y: unitsSold[i])
print("double \(Double(i))")
dataEntries.append(dataEntry)
}
xaxis.valueFormatter = formato
let data = LineChartData()
let dataset = LineChartDataSet(values: dataEntries, label: "Hello")
dataset.colors = [NSUIColor.red]
data.addDataSet(dataset)
self.lineChartView.gridBackgroundColor = NSUIColor.white
self.lineChartView.xAxis.drawGridLinesEnabled = true;
self.lineChartView.xAxis.labelPosition = XAxis.LabelPosition.bottom
self.lineChartView.xAxis.centerAxisLabelsEnabled = true
self.lineChartView.chartDescription?.text = "LineChartView Example"
self.lineChartView.xAxis.valueFormatter = xaxis.valueFormatter
self.lineChartView.data = data
}
override open func viewWillAppear(_ animated: Bool) {
self.lineChartView.animate(xAxisDuration: 1.0, yAxisDuration: 1.0)
}
}
#objc(LineChartFormatter)
public class LineChartFormatter: NSObject, IAxisValueFormatter{
var labels: [String] = []
public func stringForValue(_ value: Double, axis: AxisBase?) -> String {
return labels[Int(value)]
}
init(labels: [String]) {
super.init()
self.labels = labels
}
}
First, use granularity to avoid duplicated values.
Second, check your valueFormatter not to return same string if you found their values are very different. e.g. int(1.5) and int(1.9) will give you same Jan but you may want to let 1.9 return Feb
Again, you have to carefully implement your own valueFormatter if you are handling decimals
add this two lines in your code
lineChartView.xAxis.granularityEnabled = true
lineChartView.xAxis.granularity = 1.0
var dataEntries: [ChartDataEntry] = []
let financialData = data
for chartData in financialData{
let dataEntry = CandleChartDataEntry(x: chartData.date, shadowH: chartData.High, shadowL: chartData.Low, open: chartData.Open, close: chartData.Close)
dataEntries.append(dataEntry)
}
let chartDataSet = CandleChartDataSet(values: dataEntries, label: "")
chartDataSet.axisDependency = .left
chartDataSet.drawValuesEnabled = false
chartDataSet.increasingColor = UIColor.green
chartDataSet.increasingFilled = false
chartDataSet.decreasingColor = UIColor.red
chartDataSet.decreasingFilled = true
chartDataSet.barSpace = 0.25
chartDataSet.shadowColor = UIColor.darkGray
chartDataSet.neutralColor = UIColor.blue
let chartData = CandleChartData(dataSet: chartDataSet)
chartView.data = chartData
let xaxis = chartView.xAxis
xaxis.valueFormatter = axisFormatDelegate
Below is the sample data set that is being received from server and the image showing the chart being displayed.
https://api.cryptowat.ch/markets/gdax/btcusd/ohlc?periods=60
[ CloseTime, OpenPrice, HighPrice, LowPrice, ClosePrice, Volume ]
I need help in fixing the display of data as currently it doesn't resemble a candle stick chart in any way. Can someone please help me in what I am doing wrong here.
CandleStick chart data being displayed in the app
So just in case anyone is having the same problem, i was having issues with the candles too and i found a solution based on this post:
https://github.com/danielgindi/Charts/issues/2742
Create a class for your chartView.xAxis.valueFormatter that supports your entries with your init.
Once you have your results entries, i used the enumerated value for the x on the CandleChartDataEntry
In the stringForValue method inside your class that inherits from IAxisValueFormatter work the way you want to present your string, in my case was dates.
First two points:
//Check nil results
if result != nil {
//Save to Holder for future use
self.historyData = result
// Set the formatter class for the chartView
self.chartView.xAxis.valueFormatter = dateFormatter(elementsForFormatter: self.historyData!)
//Create a new chart data entry
var chartEntries = [ChartDataEntry]()
//Loop results
result?.enumerated().forEach({ (i, element) in
// I'm force unwrapping, but please take care of this don't do me...
let newDataEntryValue = CandleChartDataEntry(x:Double(i),
shadowH: element["high"] as! Double,
shadowL: element["low"] as! Double,
open: element["open"] as! Double,
close: element["close"] as! Double)
chartEntries.append(newDataEntryValue)
})
}
// Do rest of conf. for your chart....
Last point (class implementation):
class dateFormatter: IAxisValueFormatter {
var elements:Array<Dictionary<String,Any>>
init(elementsForFormatter:Array<Dictionary<String,Any>>) {
self.elements = elementsForFormatter
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let formatter = DateFormatter()
formatter.dateStyle = .short
//Get the date based on the value which is the index
if let time = self.elements[Int(value)]["time"] as? Double {
print(time)
let dateString = formatter.string(from: Date(timeIntervalSince1970: TimeInterval(time)))
return dateString
} else {
return ""
}
}
}
Hope this helps someone!
Recently I added https://github.com/danielgindi/Charts to my project where I have a segmentControl with time range (1D, 3M, 1Y, 5Y) to perform network call and fetch data from Yahoo.
First, I had to add a method for my String values for x-axis with the following:
class XAxisStringValueFormatter: NSObject, IAxisValueFormatter {
private var sValues: [String] = []
init(values: [String]) {
sValues = values
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
return sValues[Int(value)]
}
}
In my LineChartViewController, I have a delegate for the timeRange change to perform my networkCall:
func networkCall(range: ChartRange) {
let symbol = self.symbol
YahooFinanceApiClient.requestEquityChartpoints(symbol: symbol, range: range, onSuccess: { chartpts in
self.createLineChart(range: range, chartpoints: chartpts)
}, onError: { error in
print("Error: \(error.localizedDescription)")
})
}
Here is my create Line ChartView:
func createLineChart(range: ChartRange, chartpoints: [EquityChartpoint]) {
// set data
var xValues: [String] = []
var yValues: [Double] = []
for object in chartpoints {
switch range {
case .OneDay:
let x = Formatters.sharedInstance.stringFromHours(date: object.date)
xValues.append(x!)
case .ThreeMonth, .OneYear, .FiveYear:
let x = Formatters.sharedInstance.stringFromDate(date: object.date)
xValues.append(x!)
}
let y = object.close
yValues.append(y!)
}
// charting
let formatter: XAxisStringValueFormatter = XAxisStringValueFormatter(values: xValues)
let xaxis: XAxis = XAxis()
var dataEntries: [ChartDataEntry] = []
for i in 0..<xValues.count {
let dataEntry = ChartDataEntry(x: Double(i), y: yValues[i], data: xValues as AnyObject)
dataEntries.append(dataEntry)
}
xaxis.valueFormatter = formatter
let lineChartDataSet = LineChartDataSet(values: dataEntries, label: "Price")
let data: LineChartData = LineChartData(dataSets: [lineChartDataSet])
self.lineChartView.data = data
self.lineChartView.xAxis.valueFormatter = xaxis.valueFormatter
}
I have a Fatal Error - Index out of range when I moved from "3M" to "1Y". I checked the xValues count to match yValues for each time range. It appears the error arrived at that xAxisStringValueFormatter where yValues count does not match the xValues. Seems strange to me. Anyone has some idea what I did wrong?
Index out of range for line chart graph or Bar chart Graph. When the reading is 1. Please do the following changes in the Protocol of IAxisValueFormatter.
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
return myArr[Int(value) % myArr.count]
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let a = ""
if(myArr.count>0)
{
return myArr[Int(value) % myArr.count]
}
return a
}
I am using Daniel's IOS Chart framework and Xcode7.3.1. I went through basic tutorial and tried to set up first example.
Problem is I cannot assign X-axis string label
I see in tutorial when we assign data to chart, we should use
let chartData = BarChartData(xVals: dataPoints, dataSet: chartDataSet)
to complete last step, but below is what I am seeing here.
There is no parameter as "xVals" but only "dataset" is available.
Does anybody have idea?
The result would be just barchart without any xaxis label as below
Ok so here is example function for line chart:
func setLineChart(dataPoints: [Double], chartInfo: String, chartDescription: String) {
let formatter:ChartFormatter = ChartFormatter()
let xaxis:XAxis = XAxis()
var dataEntries: [ChartDataEntry] = Array()
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(x: Double(i), y: dataPoints[i])
dataEntries.append(dataEntry)
formatter.stringForValue(Double(i), axis: xaxis)
}
let lineChartDataSet = LineChartDataSet(values: dataEntries, label: chartInfo)
let lineChartData = LineChartData(dataSet: lineChartDataSet)
xaxis.valueFormatter = formatter
lineChartView.xAxis.valueFormatter = xaxis.valueFormatter
lineChartDataSet.mode = .CubicBezier
lineChartDataSet.drawCirclesEnabled = false
lineChartView.descriptionText = chartDescription
lineChartView.data = lineChartData
}
Here is formatter:
#objc(ChartFormatter)
public class ChartFormatter: NSObject, IAxisValueFormatter{
var days: [String] = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
public func stringForValue(value: Double, axis: AxisBase?) -> String{
return days[Int(value)]
}
}
And finally a call of function:
override func viewDidLoad() {
super.viewDidLoad()
let values: [Double] = [1.0, 4.0, 1.5, 1.4, 1.3, 1.0, 3.0]
let chartInfo = "chart info"
let chartDescription = "chart description"
self.setLineChart(values, chartInfo: chartInfo, chartDescription: chartDescription
}
so finally you will get something like this:
Seems like you are using old tutorials! Because the swift 3 is coming the the developers of these charts changed library .. according to them we will have to use special formatters to set for example values String of type to X axis. So now you have method which accepts only one parameter instead of two:
let barChartData = BarChartData(dataSet: chartDataSet)
let lineChartData = LineChartData(dataSet: lineChartDataSet)
More information you can find here:
https://github.com/danielgindi/Charts/issues/1340