Render multiple BarChartDataSets using danielgindi/Charts - charts

I'm trying to create a bar chart using danielgindi/Charts (based on MPAndroidChart). I would like to use multiple data sets, but when I try to provide multiple sets to the BarChartView no bars are rendered.
let barView = BarChartView()
let set1 = BarChartDataSet()
set1.addEntry(BarChartDataEntry(x: 1, y: 1))
let set2 = BarChartDataSet()
set2.addEntry(BarChartDataEntry(x: 2, y: 2))
barView.data = BarChartData(dataSets: [set1, set2])
contentView = barView
In the above example, I would expect two bars to be displayed. However, no bars will appear at all.
If I provide only one data set, the chart will render as expected.
barView.data = BarChartData(dataSets: [set1])

The problem is that with multiple groups, the position of the bars on the x-axis will change. However, the axis minimum and maximum values do not automatically resize to fit the new data. They must be resized manually. For example:
let groupSpace = 0.3
let barSpace = 0.0
let groupWidth = barData.groupWidth(groupSpace: groupSpace, barSpace: barSpace)
columnView.xAxis.axisMinimum = 0
columnView.xAxis.axisMaximum = groupWidth * numOfEntriesPerSet
columnView.groupBars(fromX: 0, groupSpace: groupSpace, barSpace: barSpace)

Related

iOS Charts Github :: Spacing

I'm using ios-charts to build a line chart and have been customizing it's design. However I'm having some issues with changing some settings/designs right now. This is what it currently looks like.
I can't seem to add vertical spacing between the legend and the graphs x axis labels. They are too close together.
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom // Emphasis
xAxis.labelFont = UIFont.systemFont(ofSize: 12, weight: .regular)
xAxis.axisLineWidth = 1
xAxis.axisMinimum = 239
xAxis.axisMaximum = 249
//xAxis.yOffset = 15 // ONLY ADDS TOP SPACE
// Legend
chartView.rightAxis.enabled = false // remove from the right side
chartView.legend.font = UIFont.systemFont(ofSize: 12, weight: .regular)
chartView.legend.yEntrySpace = 10
chartView.legend.formSize = 10
chartView.legend.yOffset = 10 // ONLY ADDS BOTTOM SPACE
Setting the verticalAlignment to VerticalAlignment.top (it is VerticalAlignment.bottom by default) should use the yOffset as the top space.
You can use this parameter:
chartView.legend.yOffset

SWIFT Syntax question with regards to a CLOSURE

maybe one could be so kind as to explain me this snippet
There is this nice tutorial about Core Graphics on raywenderlich. Unfortunately, the comments on that page are closed
The author declares
//Weekly sample data
var graphPoints = [4, 2, 6, 4, 5, 8, 3]
Note the "s" at the end of graphPoints. Then, to calculate the y coordinate for a chart with such figures, he uses graphPoint (without an "s" at the end) within a closure. Nevertheless the code runs just fine to my confusion.
// calculate the y point
let topBorder = Constants.topBorder
let bottomBorder = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnYPoint = { (graphPoint: Int) -> CGFloat in
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
And there is no further use of graphPoint in this project (that I am aware of, using "find"). So I wonder, how are graphPoints with an "s" linked to columnYPoint.
Though I currently have no idea how the y values flow into the closure, let me already extend my question: if my values are in a 2D array with the structure [[x1, x2], [y1, y2]], how would I pass only my y (or only my x) values into this closure?
Cheers!
UPDATE
This is how columnYPoint is used, afterwards, to draw the graph:
// draw the line graph
UIColor.white.setFill()
UIColor.white.setStroke()
// set up the points line
let graphPath = UIBezierPath()
// go to start of line
graphPath.move(to: CGPoint(x: columnXPoint(0), y: columnYPoint(graphPoints[0])))
// add points for each item in the graphPoints array
// at the correct (x, y) for the point
for i in 1..<graphPoints.count {
let nextPoint = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
graphPath.addLine(to: nextPoint)
}
graphPath.stroke()
As you have correctly identified, this is a closure (put into the variable called columnYPoint, giving it a name):
let columnYPoint = { (graphPoint: Int) -> CGFloat in
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
So really, it's like a function called columnYPoint:
func columnYPoint(_ graphPoint: Int) -> CGFloat {
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return graphHeight + topBorder - y // Flip the graph
}
Why did the author wrote a closure and put it into a variable, instead of writing a function? I have no idea, because I can't read minds. It's a stylistic choice by the author.
And if you look at how it is being called, this function/closure calculates the Y coordinate of the bar, given the height of the bar, graphPoint. graphPoint is the parameter of the function, so of course it is not used in the rest of the code. As you can see from the caller:
graphPath.move(to: CGPoint(x: columnXPoint(0), y: columnYPoint(graphPoints[0])))
// and
let nextPoint = CGPoint(x: columnXPoint(i), y: columnYPoint(graphPoints[i]))
columnYPoint will be called for each element in graphPoints, so graphPoint will be each value in graphPoints. We need to calculate the coordinates of every bar, after all.
There seems to also be a columnYPoint closure mentioned earlier, which calculates the X coordinate given a given bar index. You can combine these two closures to give you a single closure that gives you a single CGPoint:
let margin = Constants.margin
let graphWidth = width - margin * 2 - 4
let topBorder = Constants.topBorder
let bottomBorder = Constants.bottomBorder
let graphHeight = height - topBorder - bottomBorder
let maxValue = graphPoints.max()!
let columnPoint = { (column: Int, graphPoint: Int) -> CGPoint in
//Calculate the gap between points
let spacing = graphWidth / CGFloat(self.graphPoints.count - 1)
let x = CGFloat(column) * spacing + margin + 2
let y = CGFloat(graphPoint) / CGFloat(maxValue) * graphHeight
return CGPoint(x: x, y: graphHeight + topBorder - y) // Flip the graph
}

Pie Chart entries outside slices have different position offsets

The Charts library I'm using: Daniel Gindi - Charts
If you look at the above image, the position offset of entries outside each slice are variable both in distance from the slice as well as in alignment.
What I want:
Align the entry text to be at the center outside the slice. [Look at 1 & 3]
Keep all the slice texts equidistant from the slice. [Look at 3 & 6]
What I tried so far:
I tried to play with valueLinePart1Length and valueLinePart2Length, but this just helped a little, didn't solve the problem completely. Also I went through all the variables of PieChartDataSet.
Still haven't figured out how to fix this, can someone point out am I missing something or how to fix the issue.
Edit: Added code
let values: [Double] = [16, 6, 1, 3]
let chartEntries: [PieChartDataEntry] = pieChartEntries(for: values)
let pieChartDataSet = PieChartDataSet(values: chartEntries, label: nil)
pieChartDataSet.sliceSpace = 2
pieChartDataSet.colors = colors
pieChartDataSet.valueLineWidth = 0
pieChartDataSet.valueLinePart1Length = 0.4
pieChartDataSet.valueLinePart2Length = 0
pieChartDataSet.valueTextColor = .black
pieChartDataSet.yValuePosition = .outsideSlice
pieChartDataSet.valueLineVariableLength = false
let pieChartData = PieChartData(dataSet: pieChartDataSet)
pieChart.data = pieChartData
change the yValuePosition to .insideSlice

Get first number of type CGFloat

I have following numbers in CGFloat
375.0
637.0
995.0
I need to get the first number in CGFloat data type. For example the result for #1 must be 3.0, for #2 must be 6.0 and #3 must be 9.0
I tried the following
let width:CGFloat = 375.0
// Convert Float To String
let widthInStringFormat = String(describing: width)
// Get First Character Of The String
let firstCharacter = widthInStringFormat.first
// Convert Character To String
let firstCharacterInStringFormat = String(describing: firstCharacter)
// Convert String To CGFloat
//let firstCharacterInFloat = (firstCharacter as NSString).floatValue
//let firstCharacterInFloat = CGFloat(firstCharacter)
//let firstCharacterInFloat = NumberFormatter().number(from: firstCharacter)
Nothing seems working here. Where am I going wrong?
Update
To answer #Martin R, find below my explanation
I am implementing a grid-view (like photos app) using UICollectionView. I want the cells to be resized based on screen size for iPhone/iPad, Portrait and Landscape. Basically I don't want fixed columns. I need more columns for larger screen sizes and lesser column for smaller screen sizes. I figured that perhaps I can decide based on screen width. For example if the screen width is 375.0 then display 3 columns, If somewhere around 600 then display 6 columns, if around 1000 then display 10 columns and so on with equal width. So what I came up with is a) decide columns based on first number of the screen size and then for width divide by actual screen width. For example for a screen width of 375.0 I will have a cell size of CGSize(width: screenWidth / totalColumn) and so on.
You said:
For example if the screen width is 375.0 then display 3 columns, If somewhere around 600 then display 6 columns, if around 1000 then display 10 columns and so on with equal width.
So what you really want is not the first digit of the width (which would
for example be 1 for width = 1024 instead of the desired 10)
but the width divided by 100 and rounded down to the next integral value:
let numColumns = (width / 100.0).rounded(.down)
Or, as an integer value:
let numColumns = Int(width / 100.0)
var floatNum:CGFloat = 764.34
var numberNeg = false
if floatNum < 0{
floatNum = -1.0 * floatNum
numberNeg = true
}
var intNum = Int(floatNum)
print(intNum) //764
while (intNum>9) {
intNum = Int(intNum/10)
}
floatNum = CGFloat(intNum)
if numberNeg {
floatNum = -1.0 * floatNum
}
print(intNum)//7
print(floatNum)//7.0
try this one ...I hope it'll work

label value overlapping due to overcrowd in pie chart

I am working on pie chart. Text values of pie chart are overlapping due to multiple data as shown in figure below. I am not getting how to separate out these data with no overlapping labels.
My code is
pieChartDataSet.colors = colors
pieChartDataSet.valueFormatter = ChartValueFormatter()
pieChartDataSet.valueLinePart1Length = 0.7
pieChartDataSet.valueLinePart2Length = 0.3
//pieChartDataSet.valueLinePart1OffsetPercentage = 80.0
pieChartDataSet.valueLineColor = .black;
pieChartDataSet.valueTextColor = .black;
pieChartDataSet.valueFont = UIFont(name: "Verdana", size: 8.0)!
pieChartDataSet.valueLineVariableLength = true
pieChartDataSet.yValuePosition = .outsideSlice;
pieChartDataSet.xValuePosition = .outsideSlice;