swiftswiftuiswiftui-charts

SwiftUI Chart Bar Graph with Side-by-Side Bars


I"m try to display two sets of data on a SwiftUI Charts Bar Graph. I want to bars to display side-by-side (i.e. blue-green, blue-green for the sample image). However, what I'm getting is the two sets of bars displayed separately as shown in the image. What am I doing wrong? What I'm getting Here's my code: The structure of the data isn't fixed, so it can change if necessary.

struct ContentView: View {

var body: some View {
    let wheelDevelopmentArray = [
        (chainring: "one", eff:[75, 69, 64, 60, 56, 53, 47, 43, 39, 36, 32]),
        (chainring: "two", eff:[110, 101, 94, 88, 82, 77, 69, 63, 57, 53, 47])
    ]
    
    Chart(wheelDevelopmentArray, id: \.chainring) { wheels in
        ForEach(0..<wheels.eff.count, id: \.self) { i in
                    BarMark(
                        x: .value("gear", wheels.chainring),
                        y: .value("size", wheels.eff[i]),
                        width: 20
                    )
                    .foregroundStyle(by: .value("ChainRing", wheels.chainring))
                    .position(by: .value("ChainRing", i))
                }
            }
    .chartXAxis {
                AxisMarks (values: .stride (by: 1)) { value in
                    AxisValueLabel()
                }
            }
    .padding()
}

}


Solution

  • Because the x values were defined as the chainring values of "one" and "two", the chart is displaying the "one"s first and then the "two"s.

    To display the bars side by side, x values are needed that relate a "one" bar with a "two" bar. Since the arrays are the same size, the index works as an example:

    BarMark(x: .value("gear", String(i)),
    

    Side note - the x value is converted to a String to avoid the barmark from stacking. This might be a chart bug.

    With the x values changed, the chartXAxis labels are no longer needed. Also change the .position value to the chainring type for side by side positioning:

    .position(by: .value("ChainRing", wheels.chainring))
    

    Modified code:

    struct ContentView: View {
        
        var body: some View {
            let wheelDevelopmentArray = [
                (chainring: "one", eff:[75, 69, 64, 60, 56, 53, 47, 43, 39, 36, 32]),
                (chainring: "two", eff:[110, 101, 94, 88, 82, 77, 69, 63, 57, 53, 47])
            ]
            
            Chart(wheelDevelopmentArray, id: \.chainring) { wheels in
                ForEach(0..<wheels.eff.count, id: \.self) { i in
                    BarMark(
                        x: .value("gear", String(i)),
                        y: .value("size", wheels.eff[i]),
                        width: 20
                    )
                    .foregroundStyle(by: .value("ChainRing", wheels.chainring))
                    .position(by: .value("ChainRing", wheels.chainring))
                }
            }
            .padding()
        }
    }
    

    Result: chart with side by side bars