Home > Back-end >  SwiftUI Charts LineMark symbol touch event
SwiftUI Charts LineMark symbol touch event

Time:01-25

I have a chart: enter image description here

Create by code:

Chart {
    ForEach(viewModel.historyChar) { chartItem in
        AreaMark(
            x: .value("Date", chartItem.date),
            y: .value("Amount", chartItem.amount)
        )                   
        LineMark(
            x: .value("Date", chartItem.date),
            y: .value("Amount", chartItem.amount)
        )
        .symbol() {
            Button(action: {
                print("chartItem: ", chartItem)
            }, label: {
                Circle()
                    .fill(color)
                    .frame (width: 15)
                    .opacity(0.6)
            })
     }
}

I need to touch LineMark symbol and get chartItem. I tried to use Button inside .symbol(), but it don't works

Hot to add touch event to LineMark symbol?

CodePudding user response:

Try this simple approach, works well for me. It uses the chartOverlay and a function tapSymbol to calculate which symbol was tapped. The Circle symbol change to green when tapped.

import Foundation
import SwiftUI
import Charts

struct Measurement: Identifiable {
    var id: String
    var date: Double
    var amount: Double
    
    static let dx: CGFloat = 0.5  // <--- adjust as required
    static let dy: CGFloat = 2.0  // <--- adjust as required
    
    func isAround(x: CGFloat, y: CGFloat) -> Bool {
        return date <= (x   Measurement.dx) && date >= (x - Measurement.dx) && amount <= (y   Measurement.dy) && amount >= (y - Measurement.dy)
    }
}

struct ContentView: View {
    let measurement = [
        Measurement(id: "1", date: 2.0, amount: 11.0),
        Measurement(id: "2", date: 4.0, amount: 22.0),
        Measurement(id: "3", date: 6.0, amount: 38.0),
        Measurement(id: "4", date: 8.0, amount: 45.0),
        Measurement(id: "5", date: 10.0, amount: 30.0),
        Measurement(id: "6", date: 12.0, amount: 57.0),
        Measurement(id: "7", date: 14.0, amount: 26.0)
    ]
    
    @State var selected = "0"
    
    func tapSymbol(at location: CGPoint, proxy: ChartProxy, geometry: GeometryProxy) {
        let xPos = location.x - geometry[proxy.plotAreaFrame].origin.x
        let yPos = location.y - geometry[proxy.plotAreaFrame].origin.y
        if let pos: (Double, Double) = proxy.value(at: CGPoint(x: xPos, y: yPos)) {
            let results = measurement.filter{ $0.isAround(x: pos.0, y: pos.1) }
            if let firstId = results.first?.id {
                selected = firstId
            }
        }
    }
    
    var body: some View {
        Chart {
            ForEach(measurement) { chartItem in
                AreaMark(
                    x: .value("Date", chartItem.date),
                    y: .value("Amount", chartItem.amount)
                )
                LineMark(
                    x: .value("Date", chartItem.date),
                    y: .value("Amount", chartItem.amount)
                )
                .symbol {
                    Circle().fill(selected == chartItem.id ? Color.green : Color.red).opacity(0.6).frame(width: 30)
                }
            }
        }
        .chartOverlay { proxy in
            GeometryReader { geometry in
                ZStack(alignment: .top) {
                    Rectangle().fill(.clear).contentShape(Rectangle())
                        .onTapGesture { location in
                            tapSymbol(at: location, proxy: proxy, geometry: geometry)
                        }
                }
            }
        }
    }
    
}
  • Related