LineChartData without x values on constructor Charts Swift 3 - swift

I’m using iOS Charts.framework (https://github.com/danielgindi/Charts) in my swift 3.0 xcodeproject.
I know that "All dataset constructors have changed - they do not take an array of x-indices anymore" but how do I put strings on my x axis now if LineChartData doesn't have a "xVals" parameters anymore???
Before was so easy to do that...
let weights: [Double] = self.getWeigths()
let weightDates: [String] = self.getWeightDates()
var yValues : [ChartDataEntry] = [ChartDataEntry]()
for i in 0 ..< dateLastWeights.count {
let entry = ChartDataEntry(x: Double(i), y: weights[i])
yValues.append(entry)
}
let set: LineChartDataSet = LineChartDataSet(values: yValues, label: "First Set")
var dataSets : [LineChartDataSet] = [LineChartDataSet]()
dataSets.append(set)
let data: LineChartData = LineChartData(xVals: weightDates, dataSets: dataSets)

Since 3.0.1 version, you can also do:
lineChart.xAxis.valueFormatter = IndexAxisValueFormatter(values: labels)
lineChart.xAxis.granularity = 1.0

After trying lots of things and discussing with others developers we made it!
self.lineChart.xAxis.granularity = 1
self.lineChart.xAxis.valueFormatter = DefaultAxisValueFormatter(block: { (index, _) -> String in
return self.dateLastWeights[Int(index)]
})

Related

Change bar color in chart

I'm struggling with charts (I use the wonderful Charts library). I had another problem, the solution of which I could not find.
I have a horizontal chart, everything is relatively fine with it, but I need the new bar that I add to be of a different color and with a new label on the y-axis (in a horizontal chart, this is the x-axis).
This is what my chart looks like
This is what I need to get
This is the code where you can see how I add the bar
let barWidth = 5.3
let spaceForBar = 10.3
var n = 0
var yVals = (0..<count).map { (i) -> BarChartDataEntry in
let val = items.map{Double($0.iqLevel)}[n]
n += 1
return BarChartDataEntry(x: Double(i)*spaceForBar, y: val)
}
let userData = testResultItem(group: "New user", iqLevel: 118).iqLevel
let newEntry = BarChartDataEntry(x: 2.5*spaceForBar, y: Double(userData))
yVals.append(newEntry)
let chartDataSet = BarChartDataSet(entries: yVals)
I would be immensely grateful to everyone who can help at least some advice!
You can create a separate BarChartDataSet for this single user entry and set a different color for it.
let yVals = (0..<count).map { (i) -> BarChartDataEntry in
let val = items.map{Double($0.iqLevel)}[n]
n += 1
return BarChartDataEntry(x: Double(i)*spaceForBar, y: val)
}
let userData = testResultItem(group: "New user", iqLevel: 118).iqLevel
let userEntry = BarChartDataEntry(x: 2.5*spaceForBar, y: Double(userData))
let chartDataSet = BarChartDataSet(entries: yVals)
chartDataSet.setColor(YourCurrentColor)
let userDataSet = BarChartDataSet(entries: [userEntry])
userDataSet.setColor(DarkGreenColor) //For user entry
let barChartData = BarChartData(dataSets: [chartDataSet, userDataSet])

How can I enable decimals in my chart in Swift?

I am using Charts to create my own charts but I am struggling to see the values of my table with decimals instead of just integers.
My chart init is like below, but I cannot find the way to format the points to show the decimal parts since they are doubles. I have tried to set targetChartView.xAxis.decimals and targetChartView.leftAxis.decimals without result.
How can I enable the decimal notation?
init(withGraphView aGraphView: LineChartView, noDataText aNoDataTextString: String, minValue aMinValue: Double, maxValue aMaxValue: Double, numberOfDataSets aDataSetCount: Int, dataSetNames aDataSetNameList: [String], dataSetColors aColorSet: [UIColor], andMaxVisibleEntries maxEntries: Int = 10) {
originalMaxValue = aMaxValue
originalMinValue = aMinValue
dateFormatter = DateFormatter()
targetChartView = aGraphView
lineChartData = LineChartData()
maximumVisiblePoints = maxEntries
timestamps = [Date]()
for i in 0..<aDataSetCount {
let firstEntry = ChartDataEntry(x: 0, y: 0)
var entries = [ChartDataEntry]()
entries.append(firstEntry)
let aDataSet = LineChartDataSet(values: entries, label: aDataSetNameList[i])
aDataSet.setColor(aColorSet[i])
aDataSet.lineWidth = 3
aDataSet.lineCapType = .round
aDataSet.drawCircleHoleEnabled = false
aDataSet.circleRadius = 2
aDataSet.axisDependency = .left
aDataSet.highlightEnabled = true
lineChartData.addDataSet(aDataSet)
}
targetChartView.data = lineChartData
targetChartView.noDataText = aNoDataTextString
targetChartView.chartDescription?.text = ""
targetChartView.rightAxis.drawLabelsEnabled = false
targetChartView.xAxis.drawGridLinesEnabled = false
targetChartView.xAxis.labelPosition = .bottom
targetChartView.leftAxis.drawGridLinesEnabled = false
targetChartView.dragEnabled = true
targetChartView.xAxis.granularityEnabled = true
targetChartView.xAxis.granularity = 1
targetChartView.xAxis.decimals = 0
targetChartView.leftAxis.granularityEnabled = true
targetChartView.leftAxis.granularity = 1
targetChartView.leftAxis.decimals = 0
targetChartView.xAxis.axisMinimum = Double(0)
targetChartView.xAxis.axisMaximum = Double(maximumVisiblePoints)
targetChartView.leftAxis.axisMinimum = aMinValue
targetChartView.leftAxis.axisMaximum = aMaxValue
targetChartView.setScaleEnabled(false)
super.init()
targetChartView.xAxis.valueFormatter = self
targetChartView.delegate = self
// This gesture recognizer will track begin and end of touch/swipe.
// When user presses the graph we don't want it to be moving when new data is received even when the most recent value is visible.
let clickRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress))
clickRecognizer.minimumPressDuration = 0
clickRecognizer.delegate = self
targetChartView.addGestureRecognizer(clickRecognizer)
}
The decimals property is used only if you are using the default formatter. However, I see that you are also setting
targetChartView.xAxis.valueFormatter = self
That means your class is implementing IAxisValueFormatter and its stringForValue(:axis:) method. The value in decimals (which should be nonzero) is then ignored because you have a custom formatter.
You can either remove the assignment and then your decimals should be displayed or, you will have to format your decimals correctly in stringForValue(:axis:).
You have not added this part of your implementation but the problem is probably there.
I see there is also some magic in the AxisRenderer that will probably remove decimals if the interval between values is bigger than 1. Therefore using a custom formatter for both axes is probably the best solution.
As I mentioned to #Sulthan, the issue was not in the chart itself but how the data set (aDataSet) had to be formatted, so adding the snippet below enabled three decimals on my data in the graph
init(withGraphView aGraphView: LineChartView, noDataText aNoDataTextString: String, minValue aMinValue: Double, maxValue aMaxValue: Double, numberOfDataSets aDataSetCount: Int, dataSetNames aDataSetNameList: [String], dataSetColors aColorSet: [UIColor], andMaxVisibleEntries maxEntries: Int = 10) {
originalMaxValue = aMaxValue
originalMinValue = aMinValue
dateFormatter = DateFormatter()
targetChartView = aGraphView
lineChartData = LineChartData()
maximumVisiblePoints = maxEntries
timestamps = [Date]()
//Setting 3 decimals for the number that are going to be displayed
let formatter = NumberFormatter()//ADD THIS
formatter.numberStyle = .decimal//ADD THIS
formatter.maximumFractionDigits = 3//ADD THIS
formatter.minimumFractionDigits = 3//ADD THIS
for i in 0..<aDataSetCount {
let firstEntry = ChartDataEntry(x: 0, y: 0)
var entries = [ChartDataEntry]()
entries.append(firstEntry)
let aDataSet = LineChartDataSet(values: entries, label: aDataSetNameList[i])
aDataSet.setColor(aColorSet[i])
aDataSet.lineWidth = 3
aDataSet.lineCapType = .round
aDataSet.drawCircleHoleEnabled = false
aDataSet.circleRadius = 2
aDataSet.axisDependency = .left
aDataSet.highlightEnabled = true
lineChartData.addDataSet(aDataSet)
//Use formater that allows showing decimals
lineChartData.setValueFormatter(DefaultValueFormatter(formatter: formatter))//ADD THIS
}
...}

Changing iOS BarChart xAxis label position

I am using excellent iOS Chart library for creating graphs. (https://github.com/danielgindi/Charts/)
Here is what I am be able to do:
I would to have labels like this:
Do you know how to do this?
Thanks
You can shift the index by 0.5 and set xAxis.axisMinimum to 0.0:
var entries: [BarChartDataEntry] = []
for index in 0..<datapoints.count {
let value1 = Double(datapoints[index])
let value2 = Double(datapoints[index])
entries.append(BarChartDataEntry(x: Double(index)+0.5, yValues: [value1, value2]))
}
let xvalues = ["1", "2"]
combinedChart.xAxis.valueFormatter = IndexAxisValueFormatter(values: xvalues)
combinedChart.xAxis.labelPosition = Charts.XAxis.LabelPosition.bothSided
let set = BarChartDataSet(values: entries, label: " - Description.")
combinedChart.xAxis.axisMinimum = 0.0
set.axisDependency = Charts.YAxis.AxisDependency.left
data.addDataSet(set)
Result:
There are 2 ways to complete this job.
First method
chartView.setExtraOffsets(left: 20, top: 0, right: 0, bottom: 0)
Second method
chartView.xAxis.avoidFirstLastClippingEnabled = true
You could also use offset
combinedChart.xAxis.xOffset = -0.5
I am not sure if the value is correct for your case. Try it out a little bit.

iOS Charts - single values not showing Swift

When I have multiple points in an array for a line on a line graph, everything shows perfectly.
But when there is only one point, the dot does not show. I dont know why?
the delegate is being set elsewhere, but this doesnt seem to be the issue.
The below examples shows Test 2 and Test exercise. The first image is where each has one value, the second they each have 2.
heres my code
func startChart(){
chart.dragEnabled = true
chart.legend.form = .circle
chart.drawGridBackgroundEnabled = false
let xaxis = chart.xAxis
xaxis.valueFormatter = axisFormatDelegate
xaxis.labelCount = dataSets.count
xaxis.labelPosition = .bottom
xaxis.granularityEnabled = true
xaxis.granularity = 1.0
xaxis.avoidFirstLastClippingEnabled = true
xaxis.forceLabelsEnabled = true
let rightAxis = chart.rightAxis
rightAxis.enabled = false
rightAxis.axisMinimum = 0
let leftAxis = chart.leftAxis
leftAxis.drawGridLinesEnabled = true
leftAxis.axisMinimum = 0
let chartData = LineChartData(dataSets: dataSets)
chart.data = chartData
}
If I add
chart.setVisibleXRangeMinimum(myMinDate)
the value will show correctly. however it squashes the value to the left and overlaps 2 x value dates
The only way I could get around this was to add an additional invisible line.
I created a clear line that started the day before and ended the day after my single values.
As long as there is a line on the chart that goes from one point to another, the other single values show.
var singleValue = false
for i in 0...(dataSets.count - 1) {
if dataSets[i].values.count > 1{
singleValue = true
}
}
var data = dataSets
if singleValue == false {
let minNS = Calendar.current.date(byAdding: .day, value: -1, to: minNSDate as! Date)
let maxNS = Calendar.current.date(byAdding: .day, value: 1, to: maxNSDate as! Date)
var dataEntries: [ChartDataEntry] = []
let dataEntry1 = ChartDataEntry(x:Double(String(format: "%.2f",Double((minNS?.timeIntervalSince1970)!)))!,y:00.00)
let dataEntry2 = ChartDataEntry(x:Double(String(format: "%.2f",Double((maxNS?.timeIntervalSince1970)!)))!,y:00.00)
dataEntries.append(dataEntry1)
dataEntries.append(dataEntry2)
let set = LineChartDataSet(values: dataEntries, label: "")
set.setCircleColor(UIColor.clear)
set.circleHoleColor = UIColor.clear
set.setColor(UIColor.white, alpha: 0.0)
set.drawValuesEnabled = false
data.append(set)
}
chart.chartDescription?.text = ""
let chartData = LineChartData(dataSets: data)
chart.data = chartData
I think I found a better solution. Single point is not enough to draw a line (you need at least two points) so LineChartView can't render your data. You can fix that by replace LineChartView with CombinedChartView. CombinedChartView give a possibility to mix different types of data on one chart. You can check how many data entires do you have and decide which type of DataSet will be proper.
Code example:
if dataEntry.count == 1 {
let scatterDataSet = ScatterChartDataSet(values: dataEntry, label: title)
scatterDataSet.setColor(UIColor.pmd_darkBlue)
scatterDataSet.setScatterShape(.circle)
scatterDataSet.drawValuesEnabled = false
combinedChartData.scatterData = ScatterChartData(dataSets: [scatterDataSet])
}
else {
let lineDataSet = LineChartDataSet(values: dataEntry, label: title)
lineDataSet.setColor(UIColor.pmd_darkBlue)
lineDataSet.lineWidth = 3.0
lineDataSet.drawCirclesEnabled = false
lineDataSet.drawValuesEnabled = false
combinedChartData.lineData = LineChartData(dataSets: [lineDataSet])
}
combinedChart.data = combinedChartData
You can also combine two and more types DataSets in one chart.
Important
Don't forget to add this line:
combinedChart.drawOrder = [DrawOrder.line.rawValue, DrawOrder.scatter.rawValue]
You must to write types of data types you use otherwise data will not render.

Sum the Values of Nested Custom Objects in Swift

I am trying to add up the values found within some custom objects in Swift. I have the follow code working in a Playground:
//Custom Object
class EntryPack: NSObject{
var date: String!
var duration: Double!
var customValues:[CustomValuePack] = [] //Nested array of objects declared below
}
//Another object to nest in the above
class CustomValuePack: NSObject{
var name:String!
var value: Double!
}
//Instantiate nested objects
let cv1 = CustomValuePack()
cv1.name = "Day"
cv1.value = 1.2
let cv2 = CustomValuePack()
cv2.name = "Day"
cv2.value = 1.3
let cv3 = CustomValuePack()
cv2.name = "Night"
cv2.value = 2.2
//Instantiate parent objects
let entry1 = EntryPack()
entry1.date = "bingo"
entry1.duration = 1.1
entry1.customValues = [cv1, cv2]
let entry2 = EntryPack()
entry2.date = "bingo"
entry2.duration = 2.2
let entry3 = EntryPack()
entry3.date = "dang"
entry3.duration = 3.0
//Throw it all together into an array
var package = [entry1, entry2, entry3]
//Predicate for some filtering
let predicate = NSPredicate(format:"date = %#", "bingo")
let results = (package as NSArray).filteredArrayUsingPredicate(predicate) as! [EntryPack]
//Sum all the parent object durations
let sum = results.reduce(0.0) { $0 + $1.duration }
print(sum) // = 3.3
But now I want to add up the value for each CustomValuePack where the name is "Day".
I can't figure out how to do a similar reduce on a nested array of objects. I've tried something like this, but it yields a syntax error:
let sum2 = results.reduce(0.0) { $0.customValues + $1.customValues.value } //Error
How do I sum the value in a nested array of objects? I eventually want to apply an NSPredicate to filter by name = Day as well, but I'm not that far yet.
Given entries defined as follow
let entries = [entry1, entry2, entry3]
you can extract the CustomValues with name == "Day" and sum the value field with this code
let sum = entries
.flatMap { $0.customValues }
.filter { $0.name == "Day" }
.map { $0.value }
.reduce(0, combine: +)
Improvements
You are using Swift but for some reason there still is a lot of Objective-C into your code.
This is how i would refactor your code
struct EntryPack {
let date: String
let duration: Double
let customValues:[CustomValuePack]
}
struct CustomValuePack {
let name:String
let value: Double
}
let cv1 = CustomValuePack(name: "Day", value: 1.2)
let cv2 = CustomValuePack(name: "Day", value: 1.3)
let cv3 = CustomValuePack(name: "Night", value: 2.2)
let entry1 = EntryPack(date: "bingo", duration: 1.1, customValues: [cv1, cv2])
let entry2 = EntryPack(date: "bingo", duration: 2.2, customValues: [])
let entry3 = EntryPack(date: "dang", duration: 3.0, customValues: [])
Please note that now EntryPack and CustomValuePack are structs which are value types.