How to create a grouped bar chart in Swift? - ios

I am using Charts library to display charts in my app. I have a bar chart in my app that used to work fine in Swift 2.3 and Charts version 2.2.5. However, I recently updated my app to Swift 4.1 and Charts 3.1.0, but breaking changes in Charts library break the functionality in my app.
How do I properly migrate this block of code to work in new versions?
This is the example chart that I am using:
And this is the JSON data that creates this chart:
[{"Items":[{"X":"01.03.2018","Value":10000},{"X":"02.03.2018","Value":2500}],"Name":"Group 1"},{"Items":[{"X":"01.03.2018","Value":5000}],"Name":"Group 2"}]
This is the code that I used in Swift 2.3 that worked fine:
var xVals = [String]()
var datasets = [BarChartDataSet]()
for i in 0..<json.count{
var entries = [BarChartDataEntry]()
for j in 0..<json[i]["Items"].count{
if i == 0{
xVals.append(json[i]["Items"][j]["X"].stringValue)
}
if json[i]["Items"][j]["Value"].doubleValue != 0.0{
let entry = BarChartDataEntry(value: json[i]["Items"][j]["Value"].doubleValue, xIndex: j)
entries.append(entry)
}
}
let dataset = BarChartDataSet(yVals: entries, label: json[i]["Name"].stringValue)
datasets.append(dataset)
}
let data = BarChartData(xVals: xVals, dataSets: datasets)
barChart.data = data
This is how it looks after migration:
There are 2 problems with this:
Bars are stacked instead of grouped.
xAxis does show indexes instead of dates.
This is the new Swift 4.1 code:
var xVals = [String]()
var datasets = [BarChartDataSet]()
for i in 0..<json.count{
var entries = [BarChartDataEntry]()
for j in 0..<json[i]["Items"].count{
if i == 0{
xVals.append(json[i]["Items"][j]["X"].stringValue)
}
if json[i]["Items"][j]["Value"].doubleValue != 0.0{
let entry = BarChartDataEntry(x: Double(j), y: json[i]["Items"][j]["Value"].doubleValue)
entries.append(entry)
}
}
let dataset = BarChartDataSet(values: entries, label: json[i]["Name"].stringValue)
datasets.append(dataset)
}
let data = BarChartData(dataSets: datasets)
barChart.data = data
So how do I solve those 2 problems?

[Swift 4]You need to use groupBars and valueFormatter form BarChart API.
This is just an example of code, you need to fine tuning as per your requirement.
[Edit 1]
Formula: (0.2 + 0.03) * countGourp + 0.08 = 1.00 -> interval per "group"
let data = BarChartData(dataSets: datasets)
// (0.2 + 0.03) * 2 + 0.54 = 1.00
let groupSpace = 0.54
let barSpace = 0.03
let barWidth = 0.2
data.barWidth = barWidth
barView.xAxis.axisMinimum = Double(0)
barView.xAxis.axisMaximum = Double(0) + data.groupWidth(groupSpace: groupSpace, barSpace: barSpace) * Double(2) // group count : 2
data.groupBars(fromX: Double(0), groupSpace: groupSpace, barSpace: barSpace)
barView.data = data
let x_Axis = barView.xAxis
x_Axis.valueFormatter = IndexAxisValueFormatter(values:xVals)
x_Axis.centerAxisLabelsEnabled = true
x_Axis.granularity = 1
barView.xAxis.labelPosition = .top

Related

LineChart with optional Y parameter swift

Require a LineChart where in it allows y value as nil. As when no data for that particular day(x) is there the line should just go forward.
Currently I have researched on SwiftChart & Charts but both doesn't provide that functionality.
Please refer below image for more understanding.
As you can see Sunday, Monday & Wednesday there is data Tuesday there is no data so the line just continues from Monday to Wednesday.
How can I achieve this or if there is any library that can help me achieve this.
You can just skip some X value in https://github.com/danielgindi/Charts
private func setupChart() {
let leftAxis = chart.leftAxis
leftAxis.axisMinimum = 0
leftAxis.axisMaximum = 100
leftAxis.granularity = 10
let rightAxis = chart.rightAxis
rightAxis.enabled = false
let xAxis = chart.xAxis
xAxis.axisMinimum = 0
xAxis.axisMaximum = 7
xAxis.labelPosition = .bottom
xAxis.granularity = 1
}
private func setupChartData() {
var dataEntries: [ChartDataEntry] = []
for i in 0...7 {
if i % 2 == 0 {
let value = arc4random_uniform(100) + 1
if value != 0 {
let dataEntry = ChartDataEntry(x: Double(i), y: Double(value))
dataEntries.append(dataEntry)
}
}
}
let dataSet = LineChartDataSet(entries: dataEntries, label: "")
let data = LineChartData(dataSet: dataSet)
chart.data = data
}

How to remove static string "DataSet" from Legends of Pie Chart for danielgindi/Charts ios?

I am using danielgindi/Charts for iOS/Swift. There is an extra Legend Entry with label "DataSet" displays in Pie Chart as seen in this image:
When I traced, I found there are two entries in the array of LegendEntry found in the PieChartView legend, i.e. PieChartView.legend.entries, where as I have only one object in my array.
Here is the code:
let dataSet = PieChartDataSet()
dataSet.drawIconsEnabled = false
dataSet.setColor(AppColors.selectedMenuItem)
dataSet.sliceSpace = 3
dataSet.iconsOffset = CGPoint(x: 0, y: 40)
dataSet.selectionShift = 5
var totalRevenuePer:Double = 0.0
_ = arrRevenue.map({ (objRevenue) -> Void in
if let percentage = Double(objRevenue.per ?? "0.0"), percentage != 0.0{
dataSet.append(PieChartDataEntry(value: percentage, label: "\((objRevenue.rev_center_name ?? "") + " " + objRevenue.revenue.currencyString())"))
totalRevenuePer += percentage
}
})
var colors = AppColors.TenderColors
if totalRevenuePer < 100{ colors.append(.clear) }
dataSet.colors = colors
let data = PieChartData(dataSet: dataSet)
data.setValueFormatter(PercentageFormatter())
data.setValueFont(NSUIFont.systemFont(ofSize: 11))
data.setValueTextColor(.white)
pieChart.data = data
pieChart.highlightValue(nil)
let legend = pieChart.legend
legend.textColor = .white
legend.entries.last?.label = ""
pieChart.animate(yAxisDuration: 1.4, easingOption: .easeInOutQuad)
// Refresh chart with new data
pieChart.notifyDataSetChanged()
Appreciate any help, thank you.
It's a property of PieChartDataSet
The default value, if you don't set your own, is "DataSet"
let dataSet = PieChartDataSet()
// provide your own
dataSet.label = "My Label"
// or, no label
dataSet.label = ""

How to display second y-axis to right of grouped bar chart data in iOS

I am developing one activity tracker iOS application in which I need to show a history of steps and calories in a bar chart. I used iOS Charts
for this. I am able to get the grouped charts by using this but i am not able to show two seperate axis bars for steps and calories. Below is my requirment. Please suggest me how can i achive this using iOS-Charts or suggest me any other charts written in Swift.
Grouped Bar Chart
I wrote the code using iOS Charts library as below,
func setUpDataForCharts()
{
var weekDays = [String]()
for i in 1 ... 7
{
let value = String(i)
weekDays.append(value)
}
let steps = [2000, 4000, 3500, 1300, 5000, 4800, 2400]
let calories = [20.5, 40.3, 34.0, 13.3, 50.0, 80.2, 20.0]
setChartBarGroupDataSet(weekDays, values: steps, values2: calories)
//setChart(weekDays, values: steps)
barChartView.xAxis.labelPosition = ChartXAxis.LabelPosition.Bottom
barChartView.xAxis.drawGridLinesEnabled = false
}
func setChartBarGroupDataSet(dataPoints: [String], values: [Double], values2: [Double]) {
var dataEntries: [BarChartDataEntry] = []
var dataEntries2: [BarChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(value: values[i], xIndex: i)
dataEntries.append(dataEntry)
}
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(value: values2[i], xIndex: i)
dataEntries2.append(dataEntry)
}
let chartDataSet = BarChartDataSet(yVals: dataEntries, label: "Steps")
let chartDataSet2 = BarChartDataSet(yVals: dataEntries2, label: "Calories")
chartDataSet2.colors = [UIColor(hex: "3F7FD0")]
chartDataSet.colors = [UIColor(hex: "F53609")]
let dataSets: [BarChartDataSet] = [chartDataSet,chartDataSet2]
let data = BarChartData(xVals: dataPoints, dataSets: dataSets)
barChartView.data = data
barChartView.descriptionText = ""
barChartView.rightAxis.axisMaxValue = chartDataSet2.yMax + 1.0
barChartView.rightAxis.labelCount = chartDataSet2.valueCount
barChartView.rightAxis.drawGridLinesEnabled = true
barChartView.rightAxis.drawAxisLineEnabled = false
barChartView.rightAxis.drawLabelsEnabled = true
barChartView.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .Linear)
}
Out put with the above code is,
Output
Thanks in advance!

Many Gradient Fill in same lineChart Swift for iOS-Charts

let gradientColors = [UIColor.cyanColor().CGColor, UIColor.clearColor().CGColor] // Colors of the gradient
let colorLocations:[CGFloat] = [1.0, 0.0] // Positioning of the gradient
let gradient = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), gradientColors, colorLocations) // Gradient Object
set.fill = ChartFill.fillWithLinearGradient(gradient!, angle: 90.0) // Set the Gradient
set.drawFilledEnabled = true // Draw the Gradient
Result
I want to have many fill depending of xAxis value like this :
It's possible to do this ??
Solution:
The solution is to use many dataset, from the second one, the first value for each data need to be same the last value of the previous dataset so at the end it will look like you have one dataset
ex:
let dollars1 = [1453.0,2352,5431,1442] // last value = first value of next dataset
let dollars2 = [1442.0,2234,8763,4453] // last value = first value of next dataset
let dollars3 = [4453.0,3456,7843,5678] // last value = first value of next dataset
func setChartData(months : [String]) {
var yVals1 : [ChartDataEntry] = [ChartDataEntry]()
for var i = 0; i < months.count; i++ {
yVals1.append(ChartDataEntry(value: dollars1[i], xIndex: i))
}
let set1: LineChartDataSet = LineChartDataSet(yVals: yVals1, label: "First Set")
set1.axisDependency = .Left // Line will correlate with left axis values
set1.setColor(UIColor.redColor().colorWithAlphaComponent(0.5))
set1.setCircleColor(UIColor.redColor())
set1.lineWidth = 2.0
set1.circleRadius = 6.0
set1.fillAlpha = 1
set1.fillColor = UIColor.redColor()
set1.drawFilledEnabled = true
var yVals2 : [ChartDataEntry] = [ChartDataEntry]()
for var i = 0; i < months.count; i++ {
yVals2.append(ChartDataEntry(value: dollars2[i], xIndex: i))
}
let set2: LineChartDataSet = LineChartDataSet(yVals: yVals2, label: "Second Set")
set2.axisDependency = .Left // Line will correlate with left axis values
set2.setColor(UIColor.greenColor().colorWithAlphaComponent(0.5))
set2.setCircleColor(UIColor.greenColor())
set2.lineWidth = 2.0
set2.circleRadius = 6.0
set2.fillAlpha = 1
set2.fillColor = UIColor.greenColor()
set2.drawFilledEnabled = true
var yVals3 : [ChartDataEntry] = [ChartDataEntry]()
for var i = 0; i < months.count; i++ {
yVals3.append(ChartDataEntry(value: dollars3[i], xIndex: i))
}
let set3: LineChartDataSet = LineChartDataSet(yVals: yVals3, label: "Second Set")
set3.axisDependency = .Left // Line will correlate with left axis values
set3.setColor(UIColor.blueColor().colorWithAlphaComponent(0.5))
set3.setCircleColor(UIColor.blueColor())
set3.lineWidth = 2.0
set3.circleRadius = 6.0
set3.fillAlpha = 65 / 255.0
set3.fillColor = UIColor.blueColor()
set3.drawFilledEnabled = true
//3 - create an array to store our LineChartDataSets
var dataSets : [LineChartDataSet] = [LineChartDataSet]()
dataSets.append(set1)
dataSets.append(set2)
dataSets.append(set3)
//4 - pass our months in for our x-axis label value along with our dataSets
let data: LineChartData = LineChartData(xVals: months, dataSets: dataSets)
data.setValueTextColor(UIColor.whiteColor())
//5 - finally set our data
self.lineChartView.data = data
}
Yes its possible to do that gradient fill you want ,just replace below line and add some color so you can check.
set.fill = ChartFill.fillWithLinearGradient(gradient!, angle: 90.0)
Set the angle 180 so it will work
set.fill = ChartFill.fillWithLinearGradient(gradient!, angle: 180.0)

How to make a grouped BarChart with ios-charts?

I am using ios-charts library.
I would like to group my inverter values so that each year is one group. Unfortunately, the number of monthly values per year may vary.
My data json looks like this:
{"monthlyData":[{"ERTRAG":"30.2989999055862","MONAT":"2","JAHR":"2016"},{"ERTRAG":"154.897000223398","MONAT":"1","JAHR":"2016"},{"ERTRAG":"141.996000155807","MONAT":"12","JAHR":"2015"},{"ERTRAG":"135.449000149965","MONAT":"11","JAHR":"2015"},{"ERTRAG":"319.437000751495","MONAT":"10","JAHR":"2015"},{"ERTRAG":"483.369997739792","MONAT":"9","JAHR":"2015"},{"ERTRAG":"698.112997770309","MONAT":"8","JAHR":"2015"},{"ERTRAG":"771.764000892639","MONAT":"7","JAHR":"2015"},{"ERTRAG":"736.611003398895","MONAT":"6","JAHR":"2015"},{"ERTRAG":"737.237999916077","MONAT":"5","JAHR":"2015"},{"ERTRAG":"720.181995391846","MONAT":"4","JAHR":"2015"},{"ERTRAG":"484.979001283646","MONAT":"3","JAHR":"2015"},{"ERTRAG":"249.974001646042","MONAT":"2","JAHR":"2015"},{"ERTRAG":"92.8830004036427","MONAT":"1","JAHR":"2015"},{"ERTRAG":"52.7970000207424","MONAT":"12","JAHR":"2014"},{"ERTRAG":"181.025999426842","MONAT":"11","JAHR":"2014"},{"ERTRAG":"332.789002537727","MONAT":"10","JAHR":"2014"},{"ERTRAG":"482.244999885559","MONAT":"9","JAHR":"2014"},{"ERTRAG":"602.811999320984","MONAT":"8","JAHR":"2014"},{"ERTRAG":"699.872003316879","MONAT":"7","JAHR":"2014"},{"ERTRAG":"828.212007522583","MONAT":"6","JAHR":"2014"},{"ERTRAG":"679.010004997253","MONAT":"5","JAHR":"2014"},{"ERTRAG":"635.115998744965","MONAT":"4","JAHR":"2014"},{"ERTRAG":"559.617002010345","MONAT":"3","JAHR":"2014"},{"ERTRAG":"265.135001063347","MONAT":"2","JAHR":"2014"},{"ERTRAG":"165.272998273373","MONAT":"1","JAHR":"2014"},{"ERTRAG":"134.578999936581","MONAT":"12","JAHR":"2013"},{"ERTRAG":"153.774000287056","MONAT":"11","JAHR":"2013"},{"ERTRAG":"321.733997344971","MONAT":"10","JAHR":"2013"},{"ERTRAG":"482.768000483513","MONAT":"9","JAHR":"2013"},{"ERTRAG":"692.864000797272","MONAT":"8","JAHR":"2013"},{"ERTRAG":"846.429007053375","MONAT":"7","JAHR":"2013"},{"ERTRAG":"709.758005619049","MONAT":"6","JAHR":"2013"},{"ERTRAG":"532.493996858597","MONAT":"5","JAHR":"2013"},{"ERTRAG":"462.539998054504","MONAT":"4","JAHR":"2013"},{"ERTRAG":"419.105004012585","MONAT":"3","JAHR":"2013"},{"ERTRAG":"143.189998820424","MONAT":"2","JAHR":"2013"},{"ERTRAG":"78.720000628382","MONAT":"1","JAHR":"2013"},{"ERTRAG":"90.1430006623268","MONAT":"12","JAHR":"2012"},{"ERTRAG":"155.483000457287","MONAT":"11","JAHR":"2012"},{"ERTRAG":"348.231998205185","MONAT":"10","JAHR":"2012"},{"ERTRAG":"598.037001848221","MONAT":"9","JAHR":"2012"},{"ERTRAG":"729.740003108978","MONAT":"8","JAHR":"2012"},{"ERTRAG":"676.923998832703","MONAT":"7","JAHR":"2012"},{"ERTRAG":"694.879002094269","MONAT":"6","JAHR":"2012"},{"ERTRAG":"811.281997680664","MONAT":"5","JAHR":"2012"},{"ERTRAG":"489.765002369881","MONAT":"4","JAHR":"2012"},{"ERTRAG":"538.866001605988","MONAT":"3","JAHR":"2012"},{"ERTRAG":"277.856996208429","MONAT":"2","JAHR":"2012"},{"ERTRAG":"155.854999214411","MONAT":"1","JAHR":"2012"},{"ERTRAG":"148.157999750227","MONAT":"12","JAHR":"2011"},{"ERTRAG":"230.409998774529","MONAT":"11","JAHR":"2011"}]}
I tried the following:
var months = Array<String>()
var years = Array<String>()
var dataEntries: [BarChartDataEntry] = []
var dataSets: [BarChartDataSet] = []
var overallSum = 0.0
var year = 2010 //monthlyValues[0].year
var count = 0
var chartDataSet: BarChartDataSet?
for i in 0..<monthlyValues.count {
// if year is different, create new barchartdataset
if monthlyValues[i].year != year {
years.append("\(monthlyValues[i].year)")
chartDataSet = BarChartDataSet(yVals: dataEntries, label: "\(year)")
chartDataSet!.colors = [UIColor.whiteColor()]
dataSets.append(chartDataSet!)
year = monthlyValues[i].year
dataEntries = []
//months = []
count = 0
}
months.append("\(monthlyValues[i].month)")
overallSum += monthlyValues[i].amount
let dataEntry = BarChartDataEntry(value: monthlyValues[i].amount, xIndex: count)
count++
dataEntries.append(dataEntry)
}
let chartData = BarChartData(xVals: months, dataSets: dataSets)
self.barChartView.data = chartData
This results in five series with the right year labels in the legend but there is no group separator in the chart and also the number of bars per year is either 4 or 5 (expected = 12).
Thanks for any hint on this!
let months = ["Jan", "Feb", "Mar", "Apr", "May"]
let unitsSold = [20.0, 4.0, 6.0, 3.0, 12.0]
let unitsBought = [10.0, 14.0, 60.0, 13.0, 2.0]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
barChartView.delegate = self
barChartView.noDataText = "You need to provide data for the chart."
barChartView.chartDescription?.text = "sales vs bought "
//legend
let legend = barChartView.legend
legend.enabled = true
legend.horizontalAlignment = .right
legend.verticalAlignment = .top
legend.orientation = .vertical
legend.drawInside = true
legend.yOffset = 10.0;
legend.xOffset = 10.0;
legend.yEntrySpace = 0.0;
let xaxis = barChartView.xAxis
xaxis.valueFormatter = axisFormatDelegate
xaxis.drawGridLinesEnabled = true
xaxis.labelPosition = .bottom
xaxis.centerAxisLabelsEnabled = true
xaxis.valueFormatter = IndexAxisValueFormatter(values:self.months)
xaxis.granularity = 1
let leftAxisFormatter = NumberFormatter()
leftAxisFormatter.maximumFractionDigits = 1
let yaxis = barChartView.leftAxis
yaxis.spaceTop = 0.35
yaxis.axisMinimum = 0
yaxis.drawGridLinesEnabled = false
barChartView.rightAxis.enabled = false
//axisFormatDelegate = self
setChart()
}
func setChart() {
barChartView.noDataText = "You need to provide data for the chart."
var dataEntries: [BarChartDataEntry] = []
var dataEntries1: [BarChartDataEntry] = []
for i in 0..<self.months.count {
let dataEntry = BarChartDataEntry(x: Double(i) , y: self.unitsSold[i])
dataEntries.append(dataEntry)
let dataEntry1 = BarChartDataEntry(x: Double(i) , y: self.self.unitsBought[i])
dataEntries1.append(dataEntry1)
//stack barchart
//let dataEntry = BarChartDataEntry(x: Double(i), yValues: [self.unitsSold[i],self.unitsBought[i]], label: "groupChart")
}
let chartDataSet = BarChartDataSet(values: dataEntries, label: "Unit sold")
let chartDataSet1 = BarChartDataSet(values: dataEntries1, label: "Unit Bought")
let dataSets: [BarChartDataSet] = [chartDataSet,chartDataSet1]
chartDataSet.colors = [UIColor(red: 230/255, green: 126/255, blue: 34/255, alpha: 1)]
//chartDataSet.colors = ChartColorTemplates.colorful()
//let chartData = BarChartData(dataSet: chartDataSet)
let chartData = BarChartData(dataSets: dataSets)
let groupSpace = 0.3
let barSpace = 0.05
let barWidth = 0.3
// (0.3 + 0.05) * 2 + 0.3 = 1.00 -> interval per "group"
let groupCount = self.months.count
let startYear = 0
chartData.barWidth = barWidth;
barChartView.xAxis.axisMinimum = Double(startYear)
let gg = chartData.groupWidth(groupSpace: groupSpace, barSpace: barSpace)
print("Groupspace: \(gg)")
barChartView.xAxis.axisMaximum = Double(startYear) + gg * Double(groupCount)
chartData.groupBars(fromX: Double(startYear), groupSpace: groupSpace, barSpace: barSpace)
//chartData.groupWidth(groupSpace: groupSpace, barSpace: barSpace)
barChartView.notifyDataSetChanged()
barChartView.data = chartData
//background color
barChartView.backgroundColor = UIColor(red: 189/255, green: 195/255, blue: 199/255, alpha: 1)
//chart animation
barChartView.animate(xAxisDuration: 1.5, yAxisDuration: 1.5, easingOption: .linear)
}
You got something wrong here. Right now you are creating a new DataSet for every year.
What you need to do is create as many DataSets as you want bars per group where one DataSet represents one bar in each group. (e.g. DataSet 1 represents every first bar of a group, DataSet 2 every second bar of a group, ...)
As an example, imagine a chart like this with 4 groups of 3 bars per group. Each group represents one year.
||| ||| ||| |||
The setup shown above requires 3 DataSets (one for each group)
Entries in the same group have the same x-index (e.g. in the first group all have x-index 0)
It consists of a total of 12 entries
Each DataSet contains 4 entries with Entry x-indices ranging from 0 to 3
Here you can find an example of the scenario described above and check out the documentation.
if you have noticed in above code in function chart. There is let
groupSpace = 0.3
let barSpace = 0.05
let barWidth = 0.3
// (0.3 + 0.05) * 2 + 0.3 = 1.00 -> interval per "group"
which will make equation
(groupSpace * barSpace) * n + groupSpace = 1
you need to play around this equation for n number of bars
I've tried Rajan Twanabashu solution, but it was almost the good one. Maybe it has changed since, I don't know.
Here's what I've done, after I found the groupWidth function in the BarDataChart file from Charts.
groupWidth function declaration
let groupSpace = 1 - n * (barWidth + barSpace)
Hope it could help
After diving in this equation "(0.3 + 0.05) * 2 + 0.3 = 1.00" to work on variant amount of grouping I got:
let n = Double(data.dataSets.count)
let groupSpace = 0.0031 * n
let barSpace = 0.002 * n
let barWidth = (1 - (n * barSpace) - groupSpace) / n
What is varying is barWidth.

Resources