SwiftUI Chart SectorMark Flips Pie Selection On User Input???

Confused as to why the Chart flips with each user input. The console is also output unique id for each slice which was not my intention. Not sure if the unique .id is the culprit behind the flip.

selectedCount changed to: Optional(3)

Selected slice: Optional(App.EmojiUsage(id: 69090646-0D0A-4FE8-86EC-4103608DC3F7, emojiTab: App.emojiTab.sad, count: 1))

Scheduling reset task to run in 2 seconds

Resetting selected slice and count

selectedCount changed to: Optional(1)

Selected slice: Optional(App.EmojiUsage(id: DE4A76D1-CC57-4FA0-A261-9AD1A6E28F95, emojiTab: App.emojiTab.happy, count: 2))

Scheduling reset task to run in 2 seconds

Resetting selected slice and count

selectedCount changed to: Optional(3)

Selected slice: Optional(App.EmojiUsage(id: 5052F8EA-2582-4E72-A61D-01FCCDF3DB03, emojiTab: App.emojiTab.sad, count: 1))

Scheduling reset task to run in 2 seconds

Resetting selected slice and count

selectedCount changed to: Optional(0)

Selected slice: Optional(App.EmojiUsage(id: 5C1AB577-6CFC-4BA8-A9DF-30822EF79B91, emojiTab: App.emojiTab.happy, count: 2))

Scheduling reset task to run in 2 seconds
@Model
class AppModel {
    var id: String
    var journalEntry: String
    var date: Date
    var emojiTab: emojiTab

    init(journalEntry: String, date: Date, emojiTab: emojiTab) {
        self.id = UUID().uuidString
        self.journalEntry = journalEntry
        self.date = date
        self.emojiTab = emojiTab
    }
}

struct EmojiPrompt: Identifiable {
    var id = UUID()
    var icon: RiveViewModel
    var emojitab: emojiTab
    var title: String
}

enum emojiTab: String, Codable, Plottable {
    case happy
    case sad
    case sleep
    
    var primitivePlottable: Double {
        switch self {
        case .sleep:
            return 0.0
        case .happy:
            return 1.0
        case .sad:
            return 2.0
        }
    }
}


var emojiPrompt = [
    
    EmojiPrompt(
        icon: RiveViewModel(
            fileName: "app",
            stateMachineName: "happyBtnSM",
            artboardName: "happyBtn"
        ),
        emojitab: .happy,
        title: "Happy 1"
    ),
    
    EmojiPrompt(
        icon: RiveViewModel(
            fileName: "app",
            stateMachineName: "sadBtnSM",
            artboardName: "sadBtn"
        ),
        emojitab: .sad,
        title: "Sad 2"
    ),
    
    EmojiPrompt(
        icon: RiveViewModel(
            fileName: "app",
            stateMachineName: "happyBtnSM",
            artboardName: "happyBtn"
        ),
        emojitab: .sleep,
        title: "Sleep"
    )
]

import SwiftUI
import SwiftData
import RiveRuntime
import Charts
struct SectorChartView: View {
    @Environment(\.modelContext) private var context: ModelContext
    @Binding var selectedEmojiUsage: EmojiUsage?
    @State private var selectedCount: Int?
    @Binding var selectedSlice: EmojiUsage?
    @State private var resetTask: DispatchWorkItem? // State variable for the reset task
    
    var emojiUsageData: [EmojiUsage]
    var resetDelay: TimeInterval = 2.0 // Adjustable delay for reset
    
    var body: some View {
        ZStack {
            Chart {
                ForEach(emojiUsageData) { data in
                    SectorMark(
                        angle: .value("Count", data.count),
                        innerRadius: .ratio(0.70),
                        outerRadius: selectedSlice?.emojiTab == data.emojiTab ? .ratio(1.0) : .ratio(0.75),
                        angularInset: 1.5
                    )
                    .cornerRadius(4)
                    .foregroundStyle(by: .value("Emoji", data.emojiTab.rawValue.capitalized))
                }
            }
            .chartAngleSelection(value: $selectedCount)
            .chartBackground { chartProxy in
                GeometryReader { geo in
                    let frame = geo[chartProxy.plotFrame!]
                    VStack {
                        if let selectedEmojiUsage = selectedEmojiUsage {
                            RiveViewModel(fileName: "app", stateMachineName: "\(selectedEmojiUsage.emojiTab.rawValue)BtnSM", artboardName: "\(selectedEmojiUsage.emojiTab.rawValue)Btn")
                                .view()
                                .frame(width: 120, height: 120)
                                .id(selectedEmojiUsage.emojiTab.rawValue) // Force re-render when the emojiTab changes
                        } else {
                            RiveViewModel(fileName: "app", stateMachineName: "sleepBtnSM", artboardName: "sleepBtn")
                                .view()
                                .frame(width: 120, height: 120)
                                .id("sleep") // Force re-render when default state
                        }
                    }
                    .position(x: frame.midX, y: frame.midY)
                }
            }
        }
        .onChange(of: selectedCount) { oldValue, newValue in
            // Ensure reset task is only scheduled if there is a valid new value
            guard newValue != nil else { return }
            
            resetTask?.cancel() // Cancel any existing reset task
            print("selectedCount changed to: \(String(describing: newValue))")
            
            if let newValue = newValue {
                withAnimation {
                    getSelectedSlice(value: newValue)
                }
                let task = DispatchWorkItem {
                    withAnimation(.easeIn) {
                        print("Resetting selected slice and count")
                        self.selectedSlice = nil
                        self.selectedCount = nil
                        self.selectedEmojiUsage = nil
                    }
                }
                resetTask = task
                print("Scheduling reset task to run in 2 seconds")
                DispatchQueue.main.asyncAfter(deadline: .now() + resetDelay, execute: task) // Schedule reset after specified delay
            }
        }
        .frame(width: 250, height: 250)

    }
    
    private func getSelectedSlice(value: Int) {
        var cumulativeTotal = 0
        _ = emojiUsageData.first { emojiRange in
            cumulativeTotal += emojiRange.count
            if value <= cumulativeTotal {
                selectedSlice = emojiRange
                selectedEmojiUsage = emojiRange
                print("Selected slice: \(String(describing: selectedSlice))")
                return true
            }
            return false
        }
    }
}

var emojiUsageData: [EmojiUsage] {
        let groupedEntries = Dictionary(grouping: entries, by: { $0.emojiTab })
        return groupedEntries.map { (key, value) in
            EmojiUsage(emojiTab: key, count: value.count)
        }
    }
struct EmojiUsage: Identifiable {
    var id = UUID()
    var emojiTab: emojiTab
    var count: Int
}
Answered by Storybuilder in 796336022

Took a different approach to the objective which also resolved the chart flipping after user input.

Accepted Answer

Took a different approach to the objective which also resolved the chart flipping after user input.

Did you ever figure out exactly what was causing it, or could you explain how your new approach was different? I'm having the same issue.

I had the same issue as the OP. In my case, my chart data was being recomputed when you selected a sector: .chartAngleSelection(value: $selectedCount). It was because my chart data was a computed property based on other data. I changed it so that the chart data was set only once in the view initializer since I didn't need that data to update dynamically.

SwiftUI Chart SectorMark Flips Pie Selection On User Input???
 
 
Q