SwiftUI Chart: how can I overlay multiple graph datas? - ios

here are two charts.
and I would like to show the data of object and object2 together in one chart.
Would it be possible?
Chart(db.objects, id: \.self) { object in
LineMark(
x: .value("name", object.name),
y: .value("value", Int(object.value) ?? 0)
)
}
Chart(db.objects2, id: \.self) { object2 in
LineMark(
x: .value("name", object2.name),
y: .value("value", Int(object2.value) ?? 0)
)
}

I just found the way from apple developer website
https://developer.apple.com/documentation/charts/chart/
struct ProfitOverTime {
var date: Date
var profit: Double
}
let departmentAProfit: [ProfitOverTime] = [] // ...
let departmentBProfit: [ProfitOverTime] = [] // ...
var body: some View {
Chart {
ForEach(departmentAProfit) {
LineMark(
x: .value("Date", $0.date),
y: .value("Profit A", $0.profit)
)
.foregroundStyle(.blue)
}
ForEach(departmentBProfit) {
LineMark(
x: .value("Date", $0.date),
y: .value("Profit B", $0.profit)
)
.foregroundStyle(.green)
}
RuleMark(
y: .value("Threshold", 500.0)
)
.foregroundStyle(.red)
}
}

This is an alternative solution. Since the data structure is the same, the data could be one source and displayed using one LineMark.
As an example, dept was added to the model to identify each group of data that represents a single line in the chart:
struct ProfitOverTime {
var dept: String
var date: Date
var profit: Double
}
The data combined into one array:
let data: [ProfitOverTime] = [] // ...
The foreground style is based on the department, that is, each line in the chart.
struct DepartmentChart: View {
var body: some View {
Chart(data) {
LineMark(
x: .value("Date", $0.date),
y: .value("Profit", $0.profit)
series: .value("Department", $0.dept)
)
.foregroundStyle(by: .value("Department", $0.dept))
RuleMark(
y: .value("Threshold", 500.0)
)
.foregroundStyle(.red)
}
.chartForegroundStyleScale(["Profit A": .green, "Profit B": .blue])
}
}
Series was added to identify each line by its color:
The chartForegroundStyleScale is optional since each line will automatically be colored differently. But chartForegroundStyleScale can be used to customize the line colors.

Here I am showing the data for a week. So if you want to show more than data then you can increase the number of entries. So that’s it from my side hope you understand.
Now use MultiLineChartView like this.
Complete Code:
import SwiftUI
import Charts
struct ContentView: View {
let days = ["S", "M", "T", "W", "T", "F", "S"]
let entries1 = [
ChartDataEntry(x: 1, y: 1),
ChartDataEntry(x: 2, y: 2),
ChartDataEntry(x: 3, y: 0),
ChartDataEntry(x: 4, y: 0),
ChartDataEntry(x: 5, y: 0),
ChartDataEntry(x: 6, y: 0),
ChartDataEntry(x: 7, y: 1),
]
let entries2 = [
ChartDataEntry(x: 1, y: 2),
ChartDataEntry(x: 2, y: 3),
ChartDataEntry(x: 3, y: 0),
ChartDataEntry(x: 4, y: 0),
ChartDataEntry(x: 5, y: 0),
ChartDataEntry(x: 6, y: 0),
ChartDataEntry(x: 7, y: 2)
]
var body: some View {
VStack{
Spacer()
MultiLineChartView(entries1: entries1, entries2: entries2, days: days)
.frame(height: 220)
Spacer()
}
}
}
struct MultiLineChartView : UIViewRepresentable {
var entries1 : [ChartDataEntry]
var entries2 : [ChartDataEntry]
var days: [String]
func makeUIView(context: Context) -> LineChartView {
let chart = LineChartView()
return createChart(chart: chart)
}
func updateUIView(_ uiView: LineChartView, context: Context) {
uiView.data = addData()
}
func createChart(chart: LineChartView) -> LineChartView{
chart.chartDescription?.enabled = false
chart.xAxis.drawGridLinesEnabled = false
chart.xAxis.drawLabelsEnabled = true
chart.xAxis.drawAxisLineEnabled = false
chart.xAxis.labelPosition = .bottom
chart.rightAxis.enabled = false
chart.leftAxis.enabled = false
chart.drawBordersEnabled = false
chart.legend.form = .none
chart.xAxis.labelCount = 7
chart.xAxis.forceLabelsEnabled = true
chart.xAxis.granularityEnabled = true
chart.xAxis.granularity = 1
chart.xAxis.valueFormatter = CustomChartFormatter(days: days)
chart.data = addData()
return chart
}
func addData() -> LineChartData{
let data = LineChartData(dataSets: [
//Schedule Trips Line
generateLineChartDataSet(dataSetEntries: entries1, color: UIColor(Color(#colorLiteral(red: 0.6235294118, green: 0.7333333333, blue: 0.3568627451, alpha: 1))), fillColor: UIColor(Color(#colorLiteral(red: 0, green: 0.8134518862, blue: 0.9959517121, alpha: 1)))),
//Unloadings Line
generateLineChartDataSet(dataSetEntries: entries2, color: UIColor(Color(#colorLiteral(red: 0.003921568627, green: 0.231372549, blue: 0.431372549, alpha: 1))), fillColor: UIColor(Color(#colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1))))
])
return data
}
func generateLineChartDataSet(dataSetEntries: [ChartDataEntry], color: UIColor, fillColor: UIColor) -> LineChartDataSet{
let dataSet = LineChartDataSet(entries: dataSetEntries, label: "")
dataSet.colors = [color]
dataSet.mode = .cubicBezier
dataSet.circleRadius = 5
dataSet.circleHoleColor = UIColor(Color(#colorLiteral(red: 0.003921568627, green: 0.231372549, blue: 0.431372549, alpha: 1)))
dataSet.fill = Fill.fillWithColor(fillColor)
dataSet.drawFilledEnabled = true
dataSet.setCircleColor(UIColor.clear)
dataSet.lineWidth = 2
dataSet.valueTextColor = color
dataSet.valueFont = UIFont(name: "Avenir", size: 12)!
return dataSet
}
}
class CustomChartFormatter: NSObject, IAxisValueFormatter {
var days: [String]
init(days: [String]) {
self.days = days
}
public func stringForValue(_ value: Double, axis: AxisBase?) -> String {
return days[Int(value-1)]
}
}

Related

How to get full length background colors to the grouped bars in barchart in swift

I am using Charts Framework to achieve my task of bar charts, here is the look of the barchart i have created and the one which i want to achieve.
Also please suggest how to correct xAxis values.
*Achieved chart: https://i.stack.imgur.com/YFTcI.png
I have achieved the corner Radiues to the bars by modifying the BarChartRenderer.swift class in Charts FrammeWork.
Requirement : https://i.stack.imgur.com/Ptc9c.png*
Modification in BarChartRenderer.swift class
craeted a varible
#objc open var barCornerRadius = CGFloat(3.0)
i simply replace the line
context.fill(barRect)
with the code code here
let bezierPath = UIBezierPath(roundedRect: barRect, cornerRadius: barCornerRadius)
context.addPath(bezierPath.cgPath)
context.drawPath(using: .fill)
Here is the code
import Foundation
import Charts
class BarChartView: UIView, NibLoadable {
// MARK: Variables/Properties
private var viewModel: BarChartVM!
// MARK: IBOutlets
#IBOutlet weak var barChartView: BarChartView!
// MARK: View Life Cycle
override init(frame: CGRect){
super.init(frame: frame)
self.commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.commonInit()
}
// MARK: Functions
private func commonInit() {
setUpLoadableView()
self.setupViews()
}
// MARK: Setup View Model
func setupVM(vm: BarChartVM, color: UIColor = AppColors.appGreen) {
self.viewModel = vm
self.setupBarChartData()
}
}
extension BarChartView {
private func setupViews() {
self.barChartView.chartDescription?.enabled = false
self.barChartView.chartDescription?.enabled = false
self.barChartView.highlightFullBarEnabled = true
self.barChartView.setScaleEnabled(false)
self.barChartView.legend.enabled = false
self.barChartView.minOffset = 0
self.barChartView.leftAxis.enabled = false
self.barChartView.leftAxis.axisMaximum = 24
self.barChartView.leftAxis.axisMinimum = 0
self.barChartView.xAxis.enabled = true
self.barChartView.xAxis.gridColor = .clear
self.barChartView.xAxis.axisLineWidth = 1
self.barChartView.xAxis.axisLineColor = .clear
self.barChartView.xAxis.spaceMax = 0
self.barChartView.xAxis.labelPosition = .bottom
self.barChartView.xAxis.labelFont = AppFonts.Soleto_Medium.withSize(11.0)
self.barChartView.xAxis.labelTextColor =
AppColors.subtitleColor.withAlphaComponent(0.5)
self.barChartView.xAxis.drawAxisLineEnabled = false
self.barChartView.xAxis.granularity = 1.1
self.barChartView.xAxis.granularityEnabled = true
self.barChartView.xAxis.yOffset = 0
self.barChartView.xAxis.valueFormatter = self
self.barChartView.xAxis.centerAxisLabelsEnabled = false
self.barChartView.rightAxis.enabled = false
}
private func setupBarChartData() {
self.barChartView.isUserInteractionEnabled = false
self.barChartView.data = self.generateBarData(barData: self.viewModel.getBarPointSet())
}
private func generateBarData(barData: [[BarChartVM.ChartPoint]]) -> BarChartData {
var entries1 = [BarChartDataEntry]()
for (index, data) in barData[0].enumerated() {
entries1.append(BarChartDataEntry(x: Double(index), y: data.y))
}
var entries2 = [BarChartDataEntry]()
for (index, data) in barData[1].enumerated() {
entries2.append(BarChartDataEntry(x: Double(index), y: data.y))
}
let set1 = BarChartDataSet(entries: entries1)
set1.valueTextColor = AppColors.titleColor
set1.colors = [AppColors.blueLinkColor]
set1.valueFont = AppFonts.Soleto_Medium.withSize(11.0)
set1.axisDependency = .left
let set2 = BarChartDataSet(entries: entries2)
set2.valueTextColor = AppColors.titleColor
set2.colors = [AppColors.appGreen]
set2.valueFont = AppFonts.Soleto_Medium.withSize(11.0)
set2.axisDependency = .left
let groupSpace = 0.7
let barSpace = 0.38
let barWidth = 0.25
let data: BarChartData = BarChartData(dataSets: [set1, set2])
data.barWidth = barWidth
// make this BarData object grouped
data.groupBars(fromX: -0.8, groupSpace: groupSpace, barSpace: barSpace)
let formatter = NumberFormatter()
formatter.numberStyle = .none
formatter.maximumFractionDigits = 0
formatter.multiplier = 1.0
data.setValueFormatter(DefaultValueFormatter(formatter: formatter))
return data
}
}
//MARK: - Axis Value Formatter
extension BarChartView: IAxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let dates = self.viewModel.getDateStrings()
return dates[Int(value) % dates.count]
}
}
Here is the ViewModel Class
class BarChartVM {
struct ChartPoint {
var x: Double
var y: Double
init(x: Double, y: Double) {
self.x = x
self.y = y
}
}
private var barPointSet1: [ChartPoint]!
private var barPointSet2: [ChartPoint]!
private var dateStrings: [String]!
init(barPoints: [[ChartPoint]], dates: [String]) {
self.barPointSet1 = barPoints[0]
self.barPointSet2 = barPoints[1]
self.dateStrings = dates
}
func getBarPointSet() -> [[ChartPoint]] { [self.barPointSet1, self.barPointSet2] }
func getDateStrings() -> [String] { self.dateStrings }
}
The data which i am getting in ViewModel
BAR DATA SET 1:
[
BarChartVM.ChartPoint(x: 0.0, y: 16.0),
BarChartVM.ChartPoint(x: 1.0, y: 5.0),
BarChartVM.ChartPoint(x: 2.0, y: 9.0),
BarChartVM.ChartPoint(x: 3.0, y: 19.0),
BarChartVM.ChartPoint(x: 4.0, y: 13.0),
BarChartVM.ChartPoint(x: 5.0, y: 6.0),
BarChartVM.ChartPoint(x: 6.0, y: 1.0),
BarChartVM.ChartPoint(x: 7.0, y: 6.0),
BarChartVM.ChartPoint(x: 8.0, y: 1.0),
BarChartVM.ChartPoint(x: 9.0, y: 17.0),
BarChartVM.ChartPoint(x: 10.0, y: 0.0),
BarChartVM.ChartPoint(x: 11.0, y: 22.0)
]
BAR DATA SET 2:
[
BarChartVM.ChartPoint(x: 0.0, y: 12.0),
BarChartVM.ChartPoint(x: 1.0, y: 15.0),
BarChartVM.ChartPoint(x: 2.0, y: 10.0),
BarChartVM.ChartPoint(x: 3.0, y: 12.0),
BarChartVM.ChartPoint(x: 4.0, y: 7.0),
BarChartVM.ChartPoint(x: 5.0, y: 7.0),
BarChartVM.ChartPoint(x: 6.0, y: 5.0),
BarChartVM.ChartPoint(x: 7.0, y: 2.0),
BarChartVM.ChartPoint(x: 8.0, y: 23.0),
BarChartVM.ChartPoint(x: 9.0, y: 22.0),
BarChartVM.ChartPoint(x: 10.0, y: 8.0),
BarChartVM.ChartPoint(x: 11.0, y: 8.0)
]

SwiftUI Custom picker with Arrow Shape

I am trying to create SwiftUI Custom picker with Arrow Shape like
What I have done so far is :
struct SItem: Identifiable, Hashable {
var text: String
var id = UUID()
}
struct BreadCumView: View {
var items: [SItem] = [SItem(text: "Item 1"), SItem(text: "Item 2"), SItem(text: "Item 3")]
#State var selectedIndex: Int = 0
var body: some View {
HStack {
ForEach(self.items.indices) { index in
let tab = self.items[index]
Button(tab.text) {
selectedIndex = index
}.buttonStyle(CustomSegmentButtonStyle())
}
}
}
}
struct CustomSegmentButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration
.label
.padding(8)
.background(
Color(red: 0.808, green: 0.831, blue: 0.855, opacity: 0.9)
)
}
}
How to create Shape style for Background?
With Selected Stats.
Here's an arrow Shape using #Asperi's comment:
struct ArrowShape: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
let chunk = rect.height * 0.5
path.move(to: .zero)
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: max(rect.width - chunk, rect.width / 2), y: 0))
path.addLine(to: CGPoint(x: rect.width), y: chunk)
path.addLine(to: CGPoint(x: max(rect.width - chunk, rect.width / 2), y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
}
}
}

Barchart with grouped values: center-align x-axis labels

I'm using the Charts library to generate a bar chart. This is the design I've been given, which I am trying to recreate:
This is what I've managed to build:
It's getting close, but I'm having serious issues showing the bars. For example, the x-axis labels need to be shown in the center of the group, not directly under the vertical line. And the last gray bar is not shown at all for some reason.
Code:
import Charts
import UIKit
class DayAxisValueFormatter: NSObject, IAxisValueFormatter {
public func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let day = Int(value)
return ["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"][day]
}
}
class ReportBarChart: UITableViewCell {
#IBOutlet private var chartView: BarChartView!
let groupSpace = 0.06
let barSpace = 0.02
let barWidth = 0.45
override func awakeFromNib() {
super.awakeFromNib()
chartView.backgroundColor = .reportCard
chartView.drawBarShadowEnabled = false
chartView.drawValueAboveBarEnabled = false
chartView.dragEnabled = false
chartView.setScaleEnabled(false)
chartView.pinchZoomEnabled = false
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
xAxis.labelFont = .systemFont(ofSize: 11, weight: .semibold)
xAxis.labelTextColor = .reportGrayText
xAxis.granularity = 1
xAxis.labelCount = 7
xAxis.valueFormatter = DayAxisValueFormatter()
let leftAxis = chartView.leftAxis
leftAxis.enabled = false
let rightAxis = chartView.rightAxis
rightAxis.enabled = true
rightAxis.labelFont = .systemFont(ofSize: 11, weight: .semibold)
rightAxis.labelTextColor = .reportGrayText
rightAxis.axisMinimum = 0
rightAxis.labelCount = 3
let legend = chartView.legend
legend.font = .systemFont(ofSize: 11, weight: .semibold)
legend.textColor = .white
legend.form = .circle
legend.xEntrySpace = 16
}
func configure() {
let currentValues = [
BarChartDataEntry(x: 0, y: 1),
BarChartDataEntry(x: 1, y: 2),
BarChartDataEntry(x: 2, y: 3),
BarChartDataEntry(x: 3, y: 4),
BarChartDataEntry(x: 4, y: 4),
BarChartDataEntry(x: 5, y: 1),
BarChartDataEntry(x: 6, y: 6),
]
let currentValuesSet = BarChartDataSet(entries: currentValues, label: "This week")
currentValuesSet.setColor(UIColor.reportOrange)
currentValuesSet.drawValuesEnabled = false
let previousValues = [
BarChartDataEntry(x: 0, y: 4),
BarChartDataEntry(x: 1, y: 3),
BarChartDataEntry(x: 2, y: 2),
BarChartDataEntry(x: 3, y: 1),
BarChartDataEntry(x: 4, y: 3),
BarChartDataEntry(x: 5, y: 2),
BarChartDataEntry(x: 6, y: 5),
]
let previousValuesSet = BarChartDataSet(entries: previousValues, label: "Last week")
previousValuesSet.setColor(UIColor.reportGrayChart)
previousValuesSet.drawValuesEnabled = false
let data = BarChartData(dataSets: [currentValuesSet, previousValuesSet])
data.highlightEnabled = false
data.barWidth = barWidth
data.groupBars(fromX: 0, groupSpace: groupSpace, barSpace: barSpace)
chartView.data = data
}
}
Okay, found the answer 😅
xAxis.axisMinimum = 0
xAxis.axisMaximum = 7
xAxis.centerAxisLabelsEnabled = true

Half circular progress bar with gradient swift

I want to create a half circular progress bar with gradient colour in iOS using swift. The minimum value of progress bar is 300 and maximum value of progress bar is 900. The current value of the progress bar is dynamic. I am attaching screenshot ans css for colour reference. Can someone please provide me a small working demo?
below is the CSS -
/* Layout Properties */
top: 103px;
left: 105px;
width: 165px;
height: 108px;
/* UI Properties */
background: transparent linear-gradient
(269deg, #32E1A0 0%, #EEED56 23%, #EFBF39 50%, #E59148 75%, #ED4D4D 100%)
0% 0% no-repeat padding-box;
opacity: 1;
you can create not same but similar progress bar with framework called
MKMagneticProgress
example code :-
import MKMagneticProgress
#IBOutlet weak var magProgress:MKMagneticProgress!
override func viewDidLoad() {
magProgress.setProgress(progress: 0.5, animated: true)
magProgress.progressShapeColor = UIColor.blue
magProgress.backgroundShapeColor = UIColor.yellow
magProgress.titleColor = UIColor.red
magProgress.percentColor = UIColor.black
magProgress.lineWidth = 10
magProgress.orientation = .top
magProgress.lineCap = .round
magProgress.title = "Title"
magProgress.percentLabelFormat = "%.2f%%"
}
i hope it will work ... :)
this could solve your problem in swiftui
import SwiftUI
struct ContentView: View {
#State var progressValue: Float = 0.3
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State private var degress: Double = -110
var body: some View {
VStack {
ZStack{
ProgressBar(progress: self.$progressValue)
.frame(width: 250.0, height: 250.0)
.padding(40.0).onReceive(timer) { _ in
withAnimation {
if progressValue < 0.8999996 {
progressValue += 0.0275
}
}
}
ProgressBarTriangle(progress: self.$progressValue).frame(width: 280.0, height: 290.0).rotationEffect(.degrees(degress), anchor: .bottom)
.offset(x: 0, y: -150).onReceive(timer) { input in
withAnimation(.linear(duration: 0.01).speed(200)) {
if degress < 110.0 {
degress += 10
}
print(degress)
}
}
}
Spacer()
}
}
struct ProgressBar: View {
#Binding var progress: Float
var body: some View {
ZStack {
Circle()
.trim(from: 0.3, to: 0.9)
.stroke(style: StrokeStyle(lineWidth: 12.0, lineCap: .round, lineJoin: .round))
.opacity(0.3)
.foregroundColor(Color.gray)
.rotationEffect(.degrees(54.5))
Circle()
.trim(from: 0.3, to: CGFloat(self.progress))
.stroke(style: StrokeStyle(lineWidth: 12.0, lineCap: .round, lineJoin: .round))
.fill(AngularGradient(gradient: Gradient(stops: [
.init(color: Color.init(hex: "ED4D4D"), location: 0.39000002),
.init(color: Color.init(hex: "E59148"), location: 0.48000002),
.init(color: Color.init(hex: "EFBF39"), location: 0.5999999),
.init(color: Color.init(hex: "EEED56"), location: 0.7199998),
.init(color: Color.init(hex: "32E1A0"), location: 0.8099997)]), center: .center))
.rotationEffect(.degrees(54.5))
VStack{
Text("824").font(Font.system(size: 44)).bold().foregroundColor(Color.init(hex: "314058"))
Text("Great Score!").bold().foregroundColor(Color.init(hex: "32E1A0"))
}
}
}
}
struct ProgressBarTriangle: View {
#Binding var progress: Float
var body: some View {
ZStack {
Image("triangle").resizable().frame(width: 10, height: 10, alignment: .center)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3: // RGB (12-bit)
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // RGB (24-bit)
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // ARGB (32-bit)
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (1, 1, 1, 0)
}
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}
I implemented something like your progress bar,
used this for level up - gain exp. progress.,
if you go from 300 to 1000, total bar is 1500
progressFrom: 300/1500, progressTo: 1000/1500 ..
func progressAnimation(duration: TimeInterval, progressFrom: CGFloat, progressTo: CGFloat) {
if progressFrom == 0 {
baseProgressLayer.removeFromSuperlayer()
}
let circlePath = UIBezierPath(ovalIn: CGRect(center: CGPoint(x: 93.0, y: 93.0), size: CGSize(width: 178, height: 178)))
progressLayer.path = circlePath.cgPath
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeColor = getColor(progress: progressTo).cgColor
progressLayer.lineWidth = 8.0
layer.addSublayer(progressLayer)
layer.transform = CATransform3DMakeRotation(CGFloat(90 * Double.pi / 180), 0, 0, -1)
let end = CABasicAnimation(keyPath: "strokeEnd")
end.fromValue = progressFrom
end.toValue = progressTo
end.duration = duration * 0.85
end.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
end.fillMode = CAMediaTimingFillMode.forwards
end.isRemovedOnCompletion = false
let color = CABasicAnimation(keyPath: "strokeColor")
color.fromValue = getColor(progress: progressFrom).cgColor
color.toValue = getColor(progress: progressTo).cgColor
color.duration = duration * 0.85
color.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
color.fillMode = CAMediaTimingFillMode.forwards
color.isRemovedOnCompletion = false
let group = CAAnimationGroup()
group.animations = [end, color]
group.duration = duration * 0.85
progressLayer.strokeStart = progressFrom
progressLayer.strokeEnd = progressTo
progressLayer.add(group, forKey: "move")
if progressFrom != 0 {
let color2 = CABasicAnimation(keyPath: "strokeColor")
color2.fromValue = getColor(progress: progressFrom).cgColor
color2.toValue = getColor(progress: progressTo).cgColor
color2.duration = duration * 0.85
color2.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
color2.fillMode = CAMediaTimingFillMode.forwards
color2.isRemovedOnCompletion = false
baseProgressLayer.add(color2, forKey: "baseColor")
}
}
there is a extension for getting color for progress
extension CircularProgressBar {
private func getColor (progress: CGFloat) -> UIColor{
if progress >= 0 && progress <= 0.5{
return UIColor(red: CGFloat(255 / 255), green: CGFloat((0+((108-0)*progress*2)) / 255), blue: 0, alpha: 1.0)
}
else if progress > 0.5 && progress <= 1.0 {
return UIColor(red: CGFloat((255-((255-126)*(progress-0.5)*2)) / 255), green: CGFloat((108+((211-108)*(progress-0.5)*2)) / 255), blue: CGFloat((0+((33-0)*(progress-0.5)*2)) / 255), alpha: 1.0)
}
else {
return UIColor.green
}
}
}

Bar Chart jumps up when there's more than 2 columns

I am working on a chart with data set change when the button is pressed. But it looks like when I have more than 2 bars, the chart origin point shifts.
Two columns is fine:
Creating data set:
private func buildMoodDummyBarData(period: Period) -> [BarChartDataEntry] {
if period == .week {
let entry1 = BarChartDataEntry(x: 0, y: 720)
let entry2 = BarChartDataEntry(x: 1, y: 440)
let entry3 = BarChartDataEntry(x: 2, y: 0)
let entry4 = BarChartDataEntry(x: 3, y: 30)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else if period == .month {
let entry1 = BarChartDataEntry(x: 0, y: 100)
let entry2 = BarChartDataEntry(x: 1, y: 300)
let entry3 = BarChartDataEntry(x: 2, y: 50)
let entry4 = BarChartDataEntry(x: 3, y: 30)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else {
return []
}
}
Finishing building data set and changing bar colors:
func buildMoodBarData(period: Period) -> BarChartData? {
let entries = buildMoodDummyBarData(period: period)
let set = BarChartDataSet(values: entries, label: nil)
set.highlightEnabled = false
let tempData = BarChartData(dataSet: set) // Just to find max
let max = tempData.yMax
let onePercent = max / 100
var colors = [UIColor]()
for item in entries {
let percentValue = item.y / onePercent
colors.append(setColor(value: percentValue))
}
set.colors = colors
let data = BarChartData(dataSet: set)
data.setDrawValues(true)
data.barWidth = 0.72
return data
}
// barChartView is storyboard outlet
private func makeMoodBarChart() {
barChartView.backgroundColor = UIColor.red
barChartView.chartDescription = nil
barChartView.leftAxis.enabled = false
barChartView.rightAxis.enabled = false
barChartView.legend.enabled = false
barChartView.xAxis.enabled = false
}
private func updateMoodBarChart(with data: BarChartData) {
barChartView.data = data
barChartView.fitBars = false
barChartView.fitScreen()
}
The problem is related to data set. When I replaced it to following data, problem disappeared. I think issue occurs because Y scale values was changing too much.
private func buildMoodDummyBarData(period: Period) -> [BarChartDataEntry] {
if period == .week {
let entry1 = BarChartDataEntry(x: 0, y: 10)
let entry2 = BarChartDataEntry(x: 1, y: 50)
let entry3 = BarChartDataEntry(x: 2, y: 100)
let entry4 = BarChartDataEntry(x: 3, y: 20)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else if period == .month {
let entry1 = BarChartDataEntry(x: 0, y: 100)
let entry2 = BarChartDataEntry(x: 1, y: 30)
let entry3 = BarChartDataEntry(x: 2, y: 30)
let entry4 = BarChartDataEntry(x: 3, y: 20)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else {
return []
}
}
This doesn't mean min and max value has to be exactly the same in both data sets (20 to 100), however if difference is too big (30 to 320 in first data set and 30 to 720 in second), chart baseline changes.
This is not ideal solution.
While updating Barchart with new dataset make chartdata as nil
if let bardata = buildMoodBarData(period: state) {
barChatView.data = nil
updateMoodBarChart(with: bardata)
}

Resources