iOS Charts - single values not showing Swift - 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.

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 to sort WBS format string in Realm?

I want to sort my Realm object, using one of it properties. It has WBS like format (1.1.3 ,1.1.11, etc) using String as it type.
I'm using RealmSwift 3.11.2 and I've already tried using sorted(by:) and it works! But the 1.1.10 and 1.1.11 will be sorted ahead of 1.1.2
This is the code that I'm using
tasks = tasks.sorted(byKeyPath: "wbs", ascending: true)
I expect the output will be ordered correctly, like [1.1.2, 1.1.10, 1.1.11].
Any help is appreciated, I'm drawing a blank here, is there any way I can do it in Realm ?
You may want to take a minute and do an Internet search on sorting strings.
The issue is that if you have a list of strings like this 1, 2, 3, 10 and they are sorted, they will sort to 1, 10, 2, 3.
This is because how strings are sorted; for this example they are they are generally sorted by the first letter/object in the string (1's) and then by the second letter (0) and so on, so then the strings that start with 1 get grouped up. Then 2's etc.
So you need to either pad them so the formats are the same like
01.01.02, 01.01.10, 01.01.11 etc
which will sort correctly or store the three sections in separate properties as numbers (int's)
class TriadClass {
var first = 0
var second = 0
var third = 0
init(x: Int, y: Int, z: Int) {
self.first = x
self.second = y
self.third = z
}
func getPaddedString() -> String {
let formatter = NumberFormatter()
formatter.format = "00"
let firstNum = NSNumber(value: self.first)
let secondNum = NSNumber(value: self.second)
let thirdNum = NSNumber(value: self.third)
let firstString = formatter.string(from: firstNum)
let secondString = formatter.string(from: secondNum)
let thirdString = formatter.string(from: thirdNum)
let finalString = "\(firstString!).\(secondString!).\(thirdString!)"
return finalString
}
func getPureString() -> String {
let formatter = NumberFormatter()
formatter.format = "0"
let firstNum = NSNumber(value: self.first)
let secondNum = NSNumber(value: self.second)
let thirdNum = NSNumber(value: self.third)
let firstString = formatter.string(from: firstNum)
let secondString = formatter.string(from: secondNum)
let thirdString = formatter.string(from: thirdNum)
let finalString = "\(firstString!).\(secondString!).\(thirdString!)"
return finalString
}
}
Then sort by first, second and third. To get the number for display in the UI I included to functions to format them. Note both functions could be greatly improved and shortened by I left them verbose so you could step through.
The usage would be
let myNum = TriadClass(x: 1, y: 1, z: 10)
let paddedString = myNum.getPaddedString()
print(paddedString)
let nonPaddedString = myNum.getPureString()
print(nonPaddedString)
and an output of
01.01.10
1.1.10
There are a number of other solutions as well but these two are a place to start.

Candlestick Chart with dates and time on x-axis

I am trying to create an candlestick chart using Charts Framework using with Codable as JSON passing myclass can be shown as :
struct ChartDataPair: Codable {
var DateTime: String = ""
var Open: Double = 0.0
var High: Double = 0.0
var Low: Double = 0.0
var Close: Double = 0.0
}
Which creates an array of chartDataPairs as shown :
struct ChartData: Codable {
var chartDataPairs: [ChartDataPair]
}
The value that I am fetching will be shows below a bit as example :
{"chartDataPairs":
[{
"DateTime": "2018/10/1 10:00:01",
"Open": 50.05,
"High": 50.05,
"Low": 49.00,
"Close":49.00
},
{
"DateTime": "2018/10/1 10:05:02",
"Open": 51.05,
"High": 54.06,
"Low": 40.00,
"Close":45.06
},
{
"DateTime": "2018/10/1 10:10:02",
"Open": 50.05,
"High": 64.06,
"Low": 40.00,
"Close":58.06
}]
}
The data is just a sample so just wrote 3 values. Now I have to fetch only time and convert the DateTime String to Double to plot it in x-axis of the charts. For with I m using :
var dataEntries: [ChartDataEntry] = []
guard let financialData = dataChart.self else {
return
}
for chartData in financialData{
let open = chartData.Open
let close = chartData.Close
let high = chartData.High
let low = chartData.Low
let datetime = chartData.DateTime
let formatter = DateFormatter()
formatter.dateFormat = "YYYY/MM/dd HH:mm:ss"
let yourDate = formatter.date(from: datetime)
formatter.dateFormat = "HH:mm"
let myStringafd = formatter.string(from: yourDate!)
let time = myStringafd
let components = time.characters.split { $0 == ":" } .map { (x) -> Int in return Int(String(x))! }
let hours = components[0]
let minutes = components[1]
let double1 = Double("\(hours).\(minutes)")
let dataEntry = CandleChartDataEntry(x: double1! , shadowH: high, shadowL: low, open: open, close: close)
dataEntries.append(dataEntry)
}
let chartDataSet = CandleChartDataSet(values: dataEntries, label: "")
chartDataSet.axisDependency = .left
chartDataSet.drawIconsEnabled = false
chartDataSet.shadowColor = .darkGray
chartDataSet.shadowWidth = 0.7
chartDataSet.decreasingColor = .red
chartDataSet.decreasingFilled = true // fill up the decreasing field color
chartDataSet.increasingColor = UIColor(red: 122/255, green: 242/255, blue: 84/255, alpha: 1)
chartDataSet.increasingFilled = true // fill up the increasing field color
chartDataSet.neutralColor = .blue
chartDataSet.barSpace = 1.0
chartDataSet.drawValuesEnabled = false
let chartData = CandleChartData(dataSet: chartDataSet)
candlestickView.data = chartData
I know that the conversion of the time to double ins't correct as per it is needed, Here I need some help on converting the datetime to double value.
The second issue is the bar width of the candlestick, I am unable to decrease the width of the candlestick.
And I want to fill up the x-axis with the time value like HH:MM with certain intervals like 15 mins, 50 mins, 4 hrs etc.
For which I followed few questions and suggestions here in given link below :
iOS-Charts Library: x-axis labels without backing data not showing
On this issue: candlestickView.xAxisRenderer = XAxisWeekRenderer()
isn't working. It is calling for viewporthandler, x-axis and transformation.
Though I can get the custom labels from the custom IAxisValueFormatter. The interval between the two values in the x-axis is not what I wanted it to be like in 15 mins or 50 mins or 4 hrs etc.
ios Charts 3.0 - Align x labels (dates) with plots
On the above mentioned link I am unable to get the minTimeInterval
and referenceTimeInterval
Basically What I want to do here is plot the hour and minute form the string that I am fetching from the JSON in x-axis and create a custom interval in between the values of x-axis while creating the custom x-axis labels.
My chart is currently shown as :
Candlestick Chart
I have same thing to display so I used this way to display the data
let xAxis = chartView.xAxis
var dataPoints = [String]()
for i in 0 ..< arrData.count
{
let timeStampFrom = arrData[i].time
dataPoints.append(self.stringFromTimestamp(timeStampFrom, strDateFormat: "h a"))
}
xAxis.valueFormatter = IndexAxisValueFormatter(values:dataPoints)
xAxis.setLabelCount(5, force: false)
Some needed function
func stringFromTimestamp(_ timeInterval : Double ,strDateFormat : String)->String
{
let dateFormatter = DateFormatter()
self.setDateFormat(dateFormatter,dateFormat: strDateFormat)
let date = Date(timeIntervalSince1970: TimeInterval(timeInterval))
return dateFormatter.string(from: date)
}
here is the output

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
}
...}

LineChartData without x values on constructor Charts Swift 3

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)]
})