Creating Bar Chart in SwiftUI

DevTechie Inc
Jun 11, 2022
Today, we will be building bar char in SwiftUI. For our bar chart data, we will create sample data from fictitious monthly expense app.

Our final output will look like this:

We will start with an enum to represent our months.

enum Month: String, CaseIterable {
    case jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec
}
Next, we will create our MonthlyExpense model which will have month as well as expense for that month.

struct MonthlyExpense: Identifiable {
    var id = UUID()
    var month: Month
    var value: Double
    var name: String {
        month.rawValue.capitalized
    }
}
Let’s add some sample data to our MonthlyExpense struct. We will also add a static computed property to find out max value from our sample data, which we will use to compute bar’s height by normalizing the value with a simple math formula.

extension MonthlyExpense {
    static var sampleData: [MonthlyExpense] {
        [
            MonthlyExpense(month: Month.jan, value: 5000),
            MonthlyExpense(month: Month.feb, value: 2000),
            MonthlyExpense(month: Month.mar, value: 3000),
            MonthlyExpense(month: Month.apr, value: 1002),
            MonthlyExpense(month: Month.may, value: 2320),
            MonthlyExpense(month: Month.jun, value: 4536),
            MonthlyExpense(month: Month.jul, value: 8764),
            MonthlyExpense(month: Month.aug, value: 3434),
            MonthlyExpense(month: Month.sep, value: 4545),
            MonthlyExpense(month: Month.oct, value: 9067),
            MonthlyExpense(month: Month.nov, value: 2362),
            MonthlyExpense(month: Month.dec, value: 4853)
        ]
    }
    
    static var maxValue: Double {
        let maxData = sampleData.max { exp1, exp2 in
            exp1.value < exp2.value
        }
        return maxData!.value
    }
}
We will also create a Util which will take max height for the bar chart along with current value that needs to be mapped on the chart and max value in data so we can normalize the value as shown below. (For more info on normalization please read wikipedia article here)

struct Util {
    static func normalizeData(maxBarHeight: Double, value: Double, max: Double) -> Double {
        (maxBarHeight * value) / max
    }
}
Now the only thing left to do is to build the chart.

struct BarChartSample: View {
    var sampleData = MonthlyExpense.sampleData
    @State private var animate = false
    @State private var selectedValue = "None"
    
    var body: some View {
        VStack {
            // Text to show selected bar value
            Text("Selected: \(selectedValue)")
                .bold()
                .padding()
                .frame(maxWidth: .infinity)
                .foregroundColor(.white)
                .background(Color.purple, in: RoundedRectangle(cornerRadius: 5))
            
            // bar chart
            HStack {
                
                ForEach(sampleData) { data in
                    VStack {
                        // each bar for the chart
                        ZStack(alignment: .bottom) {
                            RoundedRectangle(cornerRadius: 5)
                                .fill(.pink.opacity(0.05))
                                .frame(width: 20, height: 300)
                            
                            RoundedRectangle(cornerRadius: 5)
                                .fill(LinearGradient(colors: [.purple, .blue], startPoint: .bottom, endPoint: .top))
                            
                            // calculating and plotting bar based on chart's height
                                .frame(width: 20, height: animate ? Util.normalizeData(maxBarHeight: 250, value: data.value, max: MonthlyExpense.maxValue) : 0)
                                .animation(.easeInOut.speed(0.3).delay(1), value: animate)
                        }
                        
                        // month labels
                        Text(data.name)
                            .font(.caption2)
                            .layoutPriority(1)
                            .rotationEffect(.degrees(-45))
                            .padding(.top)
                            
                    }
                    // show expense value for selected month
                    .onTapGesture {
                        selectedValue = String(format: "%@ $%.0f", data.name, data.value)
                    }
                    
                }
            }
            .onAppear {
                animate.toggle()
            }
        }
    }
}



With that we have reached the end of this article. Thank you once again for reading. Don’t forget to subscribe to our weekly newsletter at https://www.devtechie.com