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)
     )
}

Accepted Reply

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)
    }

  • That's exactly what i was looking for, thank you very much @Claude31 , i will try to adapt to my project

  • Great. BTW, VStack is superfluous, you can get rid of it (but keep .frame). Let us know how you handle the legend. And don't forget to close the thread by marking the correct answer.

Add a Comment

Replies

Looks like there a solution here: https://stackoverflow.com/questions/70754133/swiftui-half-donut-chart

  • Hi @Claude31 thanks for sharing. I saw that response from StackOverflow, but it is not using the new SectorMark, like this: https://developer.apple.com/videos/play/wwdc2023/10037/ I want to know if it is possible achieve the half donut using this new SectorMark

  • Hi @Claude31, thanks for sharing! I saw that post from StackOverflow but i believe the person is not using the SectorMark. I created my based on this video from last WWDC: https://developer.apple.com/videos/play/wwdc2023/10037/ The post from StackOverflow is more than two year ago, i would like to know if it is possible achieve it using SectorMark

  • Hi, I tried to play with angularInset, to no avail. I did not find anything in documentation. .init(angle: PlottableValue<some Plottable>, innerRadius: MarkDimension = .automatic, outerRadius: MarkDimension = .automatic, angularInset: CGFloat? = nil)

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)
    }

  • That's exactly what i was looking for, thank you very much @Claude31 , i will try to adapt to my project

  • Great. BTW, VStack is superfluous, you can get rid of it (but keep .frame). Let us know how you handle the legend. And don't forget to close the thread by marking the correct answer.

Add a Comment