Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI charts clickable annotations

I am trying to create a tap gesture on an annotation in SwiftUI's BarMark:

var body: some View {
       List {
           Chart {
               ForEach(data) {
                   BarMark(
                       x: .value("Mount", $0.mount),
                       y: .value("Value", $0.value)
                   )
                   .foregroundStyle(by: .value("Type", "Series \($0.type)"))
                   .position(by: .value("Type", $0.type))
                   .annotation {
                       HStack {
                           Rectangle()
                               .fill(Color.red.opacity(0.2))
                               .frame(width: 20, height: 20)
                               .clipShape(Circle())
                               .onTapGesture {
                                   print("Tapped!") // Never called
                               }
                       }
                   }
               }
           }
           .frame(height: 250)
           .labelsHidden()
       }
   }

I also tried Button with action, Image etc.. But it seems like all interactions in annotation are disabled or I don't know what is needed.

Apple provides some code for handling a click, but I don't know how to use it for strings. Apple uses Date in their example and they don't have compare bars like me.

Any ideas, please?

enter image description here

like image 930
ankmara Avatar asked Nov 29 '25 19:11

ankmara


1 Answers

It seems like Charts is designed to be just a flattened view without any interaction. If you want to interact with the elements of the Chart, you have to use

.chartOverlay()

This function overlays a view on the chart and then you have to use a GeometryReader in order to find the specific location interacted with.

Here is the example from the Apple documentation:

Chart(data) {
  LineMark(
    x: .value("date", $0.date),
    y: .value("price", $0.price)
  )
}
.chartOverlay { proxy in
  GeometryReader { geometry in
    Rectangle().fill(.clear).contentShape(Rectangle())
        .gesture(
            DragGesture()
                .onChanged { value in
                    // Convert the gesture location to the coordiante space of the plot area.
                    let origin = geometry[proxy.plotAreaFrame].origin
                    let location = CGPoint(
                        x: value.location.x - origin.x,
                        y: value.location.y - origin.y
                    )
                    // Get the x (date) and y (price) value from the location.
                    let (date, price) = proxy.value(at: location, as: (Date, Double).self)
                    print("Location: \(date), \(price)")
                }
        )
}

}

So for your example it would be something like this:

Chart {
   ForEach(data) {
       BarMark(
          x: .value("Mount", $0.mount),
          y: .value("Value", $0.value)
       )
   }
}
.chartOverlay { proxy in
  GeometryReader { geometry in
    Rectangle().fill(.clear).contentShape(Rectangle())
      .onTapGesture { location in
        guard let value: (mount, value) = proxy.value(at: location)    else  {
           return
        }
        //Check if value is included in the data from the chart
        print("Tapped!")
      }
   }
}
like image 186
Flightfire Avatar answered Dec 02 '25 12:12

Flightfire



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!