Half Donut Chart using SectorMark

Does anyone knows if it is possible to make a half donut chart using the new one SectorMark?

I am exploring the initializers but so far no luck, this is my code like anyone else making a normal donut:

struct Score: Identifiable {
    let id = UUID()
    let type: String
    let value: Int
}

@State private var scores: [Score] = [
    .init(type: "Hits", value: 1),
    .init(type: "Misses", value: 9)
]

Chart(scores) { score in
    SectorMark(
         angle: .value("Values", score.value),
          innerRadius: .ratio(0.9),
          outerRadius: .ratio(0.7)
    )
    .foregroundStyle(
         by: .value("Type", score.type)
     )
}
Answered by Claude31 in 782332022

For what it's worth…

I achieved something by some trick:

  • adding a new sector which value is the sum of the other
  • meeting explicitly the colours of the sectors
  • rotating the view by 90°

But I lost the legend because of rotation… So that requires some more work.

struct Score: Identifiable {
    let id = UUID()
    let type: String
    let value: Int
    let color: Color
}

struct ContentView: View {
    
    @State private var scores: [Score] = [
        .init(type: "Hits", value: 1, color: .blue),
        .init(type: "Misses", value: 9, color: .green),
        .init(type: "", value: 10, color: .white)
    ]
    
    var body: some View {
        
        Chart(scores) { score in
            SectorMark(
                angle: .value("Values", score.value),
                innerRadius: .ratio(0.9),
                outerRadius: .ratio(0.7)
            )
            .foregroundStyle(score.color)
            .foregroundStyle(
                by: .value("Type", score.type)
            )
        }
        .rotationEffect(.degrees(-90))
    }
}

To get the legend, even though rotated (and the colours are the default, not the defined ones)… So may be you should add the legend later in the VStack:

    var body: some View {
        VStack {
            Chart(scores) { score in
                SectorMark(
                    angle: .value("Values", score.value),
                    innerRadius: .ratio(0.9),
                    outerRadius: .ratio(0.7)
                )
                .foregroundStyle(score.color)
                .foregroundStyle(
                    by: .value("Type", score.type)
                )
            }
            .rotationEffect(.degrees(-90))
            .chartLegend(position: .overlay) // Could try different parameters
        }
        .frame(width: 300, height: 400)
    }

Accepted Answer

For what it's worth…

I achieved something by some trick:

  • adding a new sector which value is the sum of the other
  • meeting explicitly the colours of the sectors
  • rotating the view by 90°

But I lost the legend because of rotation… So that requires some more work.

struct Score: Identifiable {
    let id = UUID()
    let type: String
    let value: Int
    let color: Color
}

struct ContentView: View {
    
    @State private var scores: [Score] = [
        .init(type: "Hits", value: 1, color: .blue),
        .init(type: "Misses", value: 9, color: .green),
        .init(type: "", value: 10, color: .white)
    ]
    
    var body: some View {
        
        Chart(scores) { score in
            SectorMark(
                angle: .value("Values", score.value),
                innerRadius: .ratio(0.9),
                outerRadius: .ratio(0.7)
            )
            .foregroundStyle(score.color)
            .foregroundStyle(
                by: .value("Type", score.type)
            )
        }
        .rotationEffect(.degrees(-90))
    }
}

To get the legend, even though rotated (and the colours are the default, not the defined ones)… So may be you should add the legend later in the VStack:

    var body: some View {
        VStack {
            Chart(scores) { score in
                SectorMark(
                    angle: .value("Values", score.value),
                    innerRadius: .ratio(0.9),
                    outerRadius: .ratio(0.7)
                )
                .foregroundStyle(score.color)
                .foregroundStyle(
                    by: .value("Type", score.type)
                )
            }
            .rotationEffect(.degrees(-90))
            .chartLegend(position: .overlay) // Could try different parameters
        }
        .frame(width: 300, height: 400)
    }

Half Donut Chart using SectorMark
 
 
Q