user27961078
user27961078

Reputation: 11

How can I make the legend of a pie chart remain static during the chart’s animation when using 'sectormark'

I was trying to make the animation of a pie chart, I was trying to make the appear layout animation. (As the user navigates to the page, the pie chart will animate, starting from the 12 o'clock position and moving clockwise until it completes.) But when I try to use the new keyword sectormark it binds the legend of the pie chart with the pie chart, so once the pie chart animation starts, the legend would follow and start rotating as well.
I am wondering if there is a way to let the legend of the pie chart remain static.

Pie chart screenshot.

Here is the full code that I have so far with the problem:

import SwiftUI
import Charts

struct Product: Identifiable {
    let id = UUID()
    let title: String
    let revenue: Double
}

struct FirstView: View {
    @State private var animateChart: Bool = false
    @State private var products: [Product] = [
        .init(title: "Annual", revenue: 0.1),
        .init(title: "Monthly", revenue: 0.2),
        .init(title: "Lifetime", revenue: 0.7)
    ]
    @State private var selectedProduct: Product?
    @State private var isDetailLocked: Bool = false
    @State private var touchLocation: CGPoint?

    var body: some View {
        VStack {
            // Refresh button
            Button(action: {
                // Reset the animation
                withAnimation(.easeInOut(duration: 0.5)) {
                    animateChart = false
                }
                // Trigger the animation after a slight delay for reset effect
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    withAnimation(.easeInOut(duration: 1.5)) {
                        animateChart = true
                    }
                }
            }) {
                Text("Refresh Chart")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding()

            ZStack {
                // Doughnut Chart section with smaller size
                Chart(products) { product in
                    SectorMark(
                        angle: .value(
                            Text(verbatim: product.title),
                            animateChart ? product.revenue : 0 // Start from 0
                        ),
                        innerRadius: .ratio(0.6),
                        angularInset: 2
                    )
                    .cornerRadius(6)
                    .foregroundStyle(
                        by: .value(
                            Text(verbatim: product.title),
                            product.title
                        )
                    )
                }
                .rotationEffect(.degrees(animateChart ? 0 : -180))
                .frame(width: 200, height: 200) // Smaller chart size

                // Adding an overlay to handle taps
                GeometryReader { geometry in
                    Color.clear
                        .contentShape(Rectangle())
                        .gesture(
                            DragGesture(minimumDistance: 0)
                                .onChanged { value in
                                    guard !isDetailLocked else { return }
                                    let touchLocation = value.location
                                    self.touchLocation = touchLocation
                                    let center = CGPoint(
                                        x: geometry.size.width / 2,
                                        y: geometry.size.height / 2
                                    )
                                    let touchAngle = angleFromPoint(center: center, point: touchLocation)
                                    let totalRevenue = products.map(\.revenue).reduce(0, +)
                                    var currentAngle: Double = 0

                                    for product in products {
                                        let productAngle = 360 * (product.revenue / totalRevenue)
                                        if touchAngle >= currentAngle && touchAngle <= currentAngle + productAngle {
                                            selectedProduct = product
                                            break
                                        }
                                        currentAngle += productAngle
                                    }
                                }
                                .onEnded { _ in
                                    if !isDetailLocked {
                                        selectedProduct = nil
                                    }
                                }
                        )
                        .simultaneousGesture(
                            TapGesture(count: 2)
                                .onEnded {
                                    isDetailLocked.toggle()
                                }
                        )
                }

                // Tooltip view for showing details
                if let selectedProduct = selectedProduct, let touchLocation = touchLocation {
                    VStack {
                        Text("\(selectedProduct.title)")
                            .font(.headline)
                            .padding(4)
                        Text("Revenue: \(selectedProduct.revenue * 100, specifier: "%.1f")%")
                            .font(.subheadline)
                            .padding(4)
                    }
                    .background(Color.white)
                    .cornerRadius(8)
                    .shadow(radius: 5)
                    .position(x: touchLocation.x, y: touchLocation.y - 50)
                    .animation(.easeInOut)
                }
            }
            .onAppear {
                // Trigger the animation after the view appears
                withAnimation(.easeInOut(duration: 1.5)) {
                    animateChart = true
                }
            }
        }
        .background(Color.white)
    }

    // Function to calculate angle from center to a given point
    private func angleFromPoint(center: CGPoint, point: CGPoint) -> Double {
        let deltaX = point.x - center.x
        let deltaY = point.y - center.y
        let radians = atan2(deltaY, deltaX)
        var degrees = radians * 180 / .pi
        if degrees < 0 {
            degrees += 360
        }
        
        // Adjust for the 90-degree rotation of the chart
        degrees += 90
        if degrees >= 360 {
            degrees -= 360
        }
        
        return degrees
    }
}

I am expecting to fix this bug, let the legend of the pie chart remain static when animation active.

Upvotes: 1

Views: 108

Answers (0)

Related Questions