Create horizontal bar chart with scpecific data - swift

I'm learning how to create charts and I'm at the point where I need to use more complex data. I created charts where the usual ranges were used.
This is what my chart looks like now
What I need to end up with.
I don't know how to change the axes data according to the array:
items = [AgeItem(ageGroup: ">60", iqLevel: 95),
AgeItem(ageGroup: "45-60", iqLevel: 118),
AgeItem(ageGroup: "35-44", iqLevel: 141),
AgeItem(ageGroup: "25-34", iqLevel: 106),
AgeItem(ageGroup: "18-24", iqLevel: 126),
AgeItem(ageGroup: "16-17", iqLevel: 100)]
The task is to create a graph that will display how many points a person from a certain age group scored when passing the test. Y-axis - age group, X-axis - test points. I also need to add a "you" chart bar that will stand out from the others.
Here is my graph code:
override func awakeFromNib() {
super.awakeFromNib()
self.setup(barLineChartView: chartView)
chartView.delegate = self
chartView.drawBarShadowEnabled = false
chartView.drawValueAboveBarEnabled = true
chartView.maxVisibleCount = 60
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
xAxis.labelFont = .systemFont(ofSize: 10)
xAxis.drawAxisLineEnabled = true
xAxis.granularity = 10
xAxis.labelTextColor = .white
let leftAxis = chartView.leftAxis
leftAxis.labelFont = .systemFont(ofSize: 10)
leftAxis.drawAxisLineEnabled = true
leftAxis.drawGridLinesEnabled = true
leftAxis.axisMinimum = 0
leftAxis.labelTextColor = .white
let rightAxis = chartView.rightAxis
rightAxis.enabled = true
rightAxis.labelFont = .systemFont(ofSize: 10)
rightAxis.drawAxisLineEnabled = true
rightAxis.axisMinimum = 0
rightAxis.labelTextColor = .white
chartView.fitBars = true
chartView.animate(yAxisDuration: 2.5)
self.setDataCount(Int(12) + 1, range: UInt32(190))
}
func setDataCount(_ count: Int, range: UInt32) {
let barWidth = 9.0
let spaceForBar = 10.0
let yVals = (0..<count).map { (i) -> BarChartDataEntry in
let mult = range + 1
let val = Double(arc4random_uniform(mult))
return BarChartDataEntry(x: Double(i)*spaceForBar, y: val)
}
let set1 = BarChartDataSet(entries: yVals)
set1.drawIconsEnabled = false
let data = BarChartData(dataSet: set1)
data.setValueFont(UIFont.poppins(size: 12))
data.barWidth = barWidth
data.setValueTextColor(.white)
chartView.data = data
}
func setup(barLineChartView chartView: BarLineChartViewBase) {
chartView.chartDescription.enabled = false
chartView.dragEnabled = true
chartView.setScaleEnabled(true)
chartView.pinchZoomEnabled = false
// ChartYAxis *leftAxis = chartView.leftAxis;
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
chartView.rightAxis.enabled = false
}

Related

Why moveViewToX() won't move my display to the last value in my bar chart? - Swift

I'm new to Swift and I'm trying to create bar chart. I manage to create a chart but for some reason moveViewToX() does not work as expected - it does not scroll to the last x values. Here is my relevant function to create a chart:
func createBarView() {
// Set basic parameters
barChartView.xAxis.labelTextColor = .black
barChartView.leftAxis.labelTextColor = .black
barChartView.leftAxis.axisMinimum = 0
barChartView.xAxis.labelPosition = XAxis.LabelPosition.bottom
barChartView.xAxis.labelRotationAngle = 0
barChartView.xAxis.granularity = 1
barChartView.xAxis.labelFont = UIFont.systemFont(ofSize: 20.0, weight: .regular)
barChartView.xAxis.drawGridLinesEnabled = false
barChartView.rightAxis.drawGridLinesEnabled = false
barChartView.leftAxis.drawGridLinesEnabled = false
barChartView.rightAxis.enabled = false
barChartView.leftAxis.enabled = false
barChartView.xAxis.labelPosition = .bottom
barChartView.clipsToBounds = true
barChartView.layer.cornerRadius = 10
barChartView.legend.enabled = false
barChartView.animate(yAxisDuration: 1.3, easingOption: .easeInOutQuart)
barChartView.scaleYEnabled = false
barChartView.scaleXEnabled = false
barChartView.pinchZoomEnabled = false
barChartView.doubleTapToZoomEnabled = false
//Get X (days) and Y data (calories)
let count = realm.objects(WorkoutData.self).count
for i in 0 ..< count {
let graphDates = realm.objects(WorkoutData.self)[i].date
allDates.append(graphDates)
let calendarDate = Calendar.current.dateComponents([.day, .year, .month], from: graphDates)
graphDays.append(String(calendarDate.day!)) // Get days array for xAxis
let graphCalories = realm.objects(WorkoutData.self)[i].calories
visosKalorijos.append(graphCalories) // Get calories array for yAxis
}
//Supply data for BarChartDataEntry
var entries = [BarChartDataEntry]()
for i in 0 ..< count { // dataPoints
let dataEntry = BarChartDataEntry(x: Double(i), y: Double(visosKalorijos[i]))
entries.append(dataEntry) //// Get x:y data
}
//Set labels of xAxis (to show days of the month)
barChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: graphDays)
// Create dataset
let set = BarChartDataSet(entries: entries, label: "-")
set.colors = ChartColorTemplates.solid()
set.valueFont = UIFont.systemFont(ofSize: 16)
let data = BarChartData(dataSet: set)
data.barWidth = Double(0.4)
barChartView.data = data
// Set visibility
barChartView.setVisibleXRangeMaximum(7)
barChartView.moveViewToX(Double(count))
}
When function is executed I can see only up to 17th day, but there are more days on the xAxis if I scroll.
After view is loaded I need to scroll to the end.
The last part - // Set visibility is where the problem is - it does not scroll automatically to the last x value. What Am I doing wrong? Thanks for your help.
After some time I figured this out. Last step //Set visibility needs delay of 0.1s:
barChartView.setVisibleXRangeMaximum(7)
let when = DispatchTime.now() + 0.1
DispatchQueue.main.asyncAfter(deadline: when) {
self.barChartView.moveViewToX(Double(self.count))
}
Hopefully this will help someone in the future :)

Line chart lines with same value are overlapping

I am using danielgindi
/
Charts for creating line chart. When Line chart data has same values, only the last drawn line showing. Other lines are hidden behind the last drawn line. Is there any way to show all lines.
image: 'fff' and 'User B' is hidden behind 'User C', they all have number of activities equal to zero
Line chart initialisation
func commonInit() {
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
lblXAxis.text = "Days"
lblYAxis.text = "Number of Activities"
lblYAxis.transform = CGAffineTransform(rotationAngle: -CGFloat.pi / 2)
self.roundAllCorners(radius: 6)
let valFormatter = NumberFormatter()
valFormatter.numberStyle = .none
valFormatter.maximumFractionDigits = 0
viewProgressLineChart.leftAxis.valueFormatter = DefaultAxisValueFormatter(formatter: valFormatter)
viewProgressLineChart.leftAxis.granularity = 1
viewProgressLineChart.xAxis.granularity = 1
viewProgressLineChart.xAxis.labelCount = 11
viewProgressLineChart.xAxis.avoidFirstLastClippingEnabled = false
viewProgressLineChart.xAxis.labelPosition = .bottom
viewProgressLineChart.chartDescription?.text = ""
viewProgressLineChart.xAxis.labelTextColor = UIColor.onPrimary
viewProgressLineChart.leftAxis.labelTextColor = UIColor.onPrimary
viewProgressLineChart.rightAxis.labelTextColor = UIColor.onPrimary
viewProgressLineChart.clipValuesToContentEnabled = true
viewProgressLineChart.legend.enabled = false
viewProgressLineChart.rightAxis.enabled = false
viewProgressLineChart.animate(xAxisDuration: 0.8)
}
This is how data is given to line chart,
func updateGraph(users: [User]){
let data = LineChartData()
for (index,user) in users.enumerated(){
let userColor = UIColor.selectedColors[index]
userAndStatusColorArray.append(UserAndStatusColor(name: user.name ?? "user", color: userColor))
var lineChartEntry = [ChartDataEntry]()
if user.progress != nil{
for progress in user.progress!{
let chartData = ChartDataEntry(x: Double(progress.day), y: Double(progress.activitiesCompleted!))
lineChartEntry.append(chartData)
}
let chartDataSet = LineChartDataSet(entries: lineChartEntry)
chartDataSet.colors = [userColor]
chartDataSet.circleColors = [userColor]
chartDataSet.circleRadius = 3
chartDataSet.drawValuesEnabled = false
data.addDataSet(chartDataSet)
}
}
viewPODProgressLineChart.viewProgressLineChart.data = data
}

Set labels at RadarChartData

I am using Charts v3.2.2 framework by danielgindi for iOS and macOS to draw a RadarChartView. There is a github repository that provides an example xcode project including Playgrounds. One is for RadarChartView.
Following the example I can set RadarChartData with two different RadarChartDataSets
var chartView = RadarChartView(frame: rect)
let data = RadarChartData(dataSets: [set1, set2])
chartView.data = data
It shows a chart like this:
The green labels set to x-axis are numbers from 0.0 to 4.0, but they should be string labels.
I cannot figure out how to set these labels that should be drawn around the RadarChart at the end of each web line. I guess it should be something like this:
data.setLabels("London", "Paris", "Berlin", "New York", "Tokio")
But this isn't working although it is a feature of class RadarChartData to set the desired labels.
Can somebody help me with that issue?
EDIT: complete code example
import Cocoa
import Charts
import PlaygroundSupport
let r = CGRect(x: 0, y: 0, width: 400, height: 400)
var chartView = RadarChartView(frame: r)
// General settings
chartView.webColor = NSUIColor.lightGray
chartView.innerWebColor = NSUIColor.lightGray
chartView.webAlpha = 1.0
// xAxis settings
let xAxis = chartView.xAxis
xAxis.xOffset = 0.0
xAxis.yOffset = 0.0
xAxis.labelTextColor = NSUIColor.green
xAxis.drawLabelsEnabled = true
// yAxis settings
let yAxis = chartView.yAxis
yAxis.labelCount = 5
yAxis.axisMinimum = 0.0
yAxis.axisMaximum = 80.0
yAxis.drawLabelsEnabled = true
// Legend settings
let legend = chartView.legend
// ... (irrelevant)
// Description
chartView.chartDescription?.enabled = true
chartView.chartDescription?.text = "Radar demo"
chartView.chartDescription?.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
// RadarChartDataEntry
let mult = 80.0
let min = 20.0
let cnt = 5
var entries1 = [RadarChartDataEntry]()
var entries2 = [RadarChartDataEntry]()
for i in 1...cnt
{
let values1 = (Double(arc4random_uniform(UInt32(mult))) + min)
entries1.append(RadarChartDataEntry(value: values1, data: "a" as AnyObject))
let values2 = (Double(arc4random_uniform(UInt32(mult))) + min)
entries2.append(RadarChartDataEntry(value: values2, data: "b" as AnyObject))
}
// RadarChartDataSet
let set1 = RadarChartDataSet(entries: entries1, label: "Last Week")
set1.drawFilledEnabled = true
set1.fillAlpha = 0.7
set1.lineWidth = 2.0
set1.drawHighlightCircleEnabled = true
set1.setDrawHighlightIndicators(false)
let set2 = RadarChartDataSet(entries: entries2, label: "This Week")
set2.drawFilledEnabled = true
set2.fillAlpha = 0.7
set2.lineWidth = 2.0
set2.drawHighlightCircleEnabled = true
set2.setDrawHighlightIndicators(false)
// RadarChartData
let data = RadarChartData(dataSets: [set1, set2])
data.setLabels("London", "Paris", "Berlin", "New York", "Tokio")
data.setDrawValues ( true )
data.setValueTextColor( NSUIColor.white )
chartView.data = data
chartView.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInBounce)
// show chartView
PlaygroundPage.current.liveView = chartView
You need to override IAxisValueFormatter func stringForValue(_ value: Double, axis: AxisBase?) -> String {} function.
Like below:
Step1: Customize your xAxis with custom formatter.
let xValues = ["X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8", "X9", "X10"]
let chartFormatter = RadarChartFormatter(labels: xValues)
let xAxis = XAxis()
xAxis.valueFormatter = chartFormatter
self.xAxis.valueFormatter = xAxis.valueFormatter
Step2: Implement Custom formatter with below method.
private class RadarChartFormatter: NSObject, IAxisValueFormatter {
var labels: [String] = []
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
if Int(value) < labels.count {
return labels[Int(value)]
}else{
return String("")
}
}
init(labels: [String]) {
super.init()
self.labels = labels
}
}
You will get below output in your RadarCharView:
Hope this will help you to get your custom labels on Radar chart!

How to align both right and left axis in the same line using ios-charts?

I have the following line chart with 2 datasets.
One data set is dependent on the left yAxis the other one on the right yAxis.
Now i want them both to have the zero value in the same line.
The other values are ok to not be aligned.
How can i do that?
Code:
let speedLine = LineChartDataSet(values: speedEntries, label: "Speed")
speedLine.colors = [UIColor.white]
speedLine.drawCirclesEnabled = false
speedLine.axisDependency = .left
let powerLine = LineChartDataSet(values: powerEntries, label: "Power")
powerLine.colors = [UIColor.green]
powerLine.drawCirclesEnabled = false
powerLine.axisDependency = .right
let data = LineChartData()
data.addDataSet(speedLine)
data.addDataSet(powerLine)
chartView.data = data
chartView.xAxis.labelTextColor = UIColor.white
chartView.xAxis.labelCount = 20
chartView.leftAxis.labelTextColor = UIColor.white
chartView.rightAxis.labelTextColor = UIColor.green
chartView.legend.textColor = UIColor.white
chartView.rightAxis.axisMinimum = -80.0
chartView.chartDescription?.text = ""
Since you have:
chartView.rightAxis.axisMinimum = -80.0
You might need to do this as well:
chartView.leftAxis.axisMinimum = -80.0
I think there is still no resolution for this problem but we can still hide yAxis right side.
let yAxisLeft = chartView.leftAxis
yAxisLeft.drawGridLinesEnabled = true
yAxisLeft.gridColor = UIColor(hex: "DCE5EE")
yAxisLeft.labelFont = Theme.Font.main(ofSize: 9, weight: .regular)
yAxisLeft.labelXOffset = 2
yAxisLeft.labelTextColor = Theme.Color.white
yAxisLeft.axisLineColor = UIColor(hex: "DCE5EE")
yAxisLeft.drawAxisLineEnabled = false
yAxisLeft.labelCount = 4
yAxisLeft.axisMinLabels = 1
yAxisLeft.enabled = true
yAxisLeft.axisMinimum = 0
let yAxisRight = chartView.rightAxis
yAxisRight.drawGridLinesEnabled = false
yAxisRight.labelFont = Theme.Font.main(ofSize: 9, weight: .regular)
yAxisRight.labelXOffset = -2
yAxisRight.labelTextColor = Theme.Color.white
yAxisRight.drawAxisLineEnabled = false
yAxisRight.labelCount = 4
yAxisRight.axisMinLabels = 1
yAxisRight.enabled = true
yAxisRight.axisMinimum = 0
This is the final result:

Change yValue label vertical spacing in combined chart (ios-charts)

I have a combined chart that uses a line and bar chart to show yValues.
In some instances the line and bar chart values will overlap, is there a way to set the vertical spacing of the labels for the yValues so that they're not on top of each other (like Jan to Oct in the image)?
Combined Chart Image
I'm using the Charts framework (formerly ios-charts), here is the code to setup the CombineChartView:
let xValues = getXAxisLabelsForYear(year)
let runningTotalsByMonth = getRunningTotalByMonthForYear(year)!
var yValsBar = [BarChartDataEntry]()
var yValsLine = [ChartDataEntry]()
for i in 0 ..< xValues.count {
let yBarDataEntry = BarChartDataEntry(value: monthlyWinnings[i], xIndex: i)
yValsBar.append(yBarDataEntry)
let yLineDataEntry = ChartDataEntry(value: runningTotalsByMonth[i], xIndex: i)
yValsLine.append(yLineDataEntry)
}
let barChartDataSet = BarChartDataSet(yVals: yValsBar, label: "Monthly Winnings")
//setup bar chart
var barChartColors = [UIColor]()
for i in monthlyWinnings {
if i >= 0.0 {
barChartColors.append(myGreen)
} else {
barChartColors.append(UIColor.redColor())
}
}
barChartDataSet.colors = barChartColors
barChartDataSet.barShadowColor = UIColor.clearColor()
barChartDataSet.valueFont = UIFont.systemFontOfSize(10.0)
//setup line chart
let lineChartDataSet = LineChartDataSet(yVals: yValsLine, label: "Cumulative Winnings")
var lineChartColors = [UIColor]()
for i in runningTotalsByMonth {
if i >= 0.0 {
lineChartColors.append(myGreen)
} else {
lineChartColors.append(UIColor.redColor())
}
}
lineChartDataSet.colors = lineChartColors
lineChartDataSet.circleColors = [UIColor.blueColor()]
lineChartDataSet.drawCircleHoleEnabled = false
lineChartDataSet.circleRadius = 5
lineChartDataSet.lineWidth = 2
lineChartDataSet.valueFont = UIFont.systemFontOfSize(10.0)
//combine data
let data = CombinedChartData(xVals: xValues)
data.barData = BarChartData(xVals: xValues, dataSet: barChartDataSet)
data.lineData = LineChartData(xVals: xValues, dataSet: lineChartDataSet)
combinedChartView.data = data
//format the chart
combinedChartView.xAxis.setLabelsToSkip(0)
combinedChartView.xAxis.labelPosition = .Bottom
combinedChartView.descriptionText = ""
combinedChartView.rightAxis.drawLabelsEnabled = false
combinedChartView.rightAxis.drawGridLinesEnabled = false
combinedChartView.drawGridBackgroundEnabled = false
combinedChartView.leftAxis.drawZeroLineEnabled = true
combinedChartView.xAxis.drawGridLinesEnabled = false
combinedChartView.xAxis.wordWrapEnabled = true
You can draw bar chart values below the top of the bar using
chartView.drawValueAboveBarEnabled = false
and setting some color
barChartDataSet.valueTextColor = UIColor.someColor()
Will look like this:
See my comment above, but something like this may work if you're not using auto layout:
let labelA = UILabel()
let labelB = UILabel()
let padding: CGFloat = 5.0 // or whatever
if CGRectIntersectsRect(labelA.frame, labelB.frame) {
// If the minY of labelA is <= labelB's that means labelA is ABOVE labelB
if labelA.frame.minY <= labelB.frame.minY {
// Set it above, with some (optional) padding
labelA.frame.origin.y = labelB.frame.origin.y - padding - labelA.frame.height
} else {
labelB.frame.origin.y = labelA.frame.origin.y - padding - labelB.frame.height
}
}
Of course you'll need additional code for checking if it's too high and other edge cases.