diff InteractiveCharts/LineChart/Helpers/LineView.swift @ 8:959175ee5ebd

Implement interaction with ChartView
author Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com>
date Mon, 26 Apr 2021 23:06:42 +0200
parents f828c7c408d4
children
line wrap: on
line diff
--- a/InteractiveCharts/LineChart/Helpers/LineView.swift	Mon Apr 26 23:06:25 2021 +0200
+++ b/InteractiveCharts/LineChart/Helpers/LineView.swift	Mon Apr 26 23:06:42 2021 +0200
@@ -9,14 +9,47 @@
 
 struct LineView: View {
     var data: [Double]
+    var dates: [String]?
+    var hours: [String]?
+    
+    @Binding var showingIndicators: Bool
+    @Binding var indexPosition: Int
+    @State private var IndicatorPointPosition: CGPoint = .zero
+    @State private var pathPoints = [CGPoint]()
     
     var body: some View {
-        GeometryReader { proxy in
-            LinePath(data: data, width: proxy.size.width, height: proxy.size.height)
-                .stroke(colorLine(), lineWidth: 2)
-                .rotationEffect(.degrees(180), anchor: .center)
-                .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
+        ZStack {            
+            GeometryReader { proxy in
+                LinePath(data: data, width: proxy.size.width, height: proxy.size.height, pathPoints: $pathPoints)
+                    .stroke(colorLine(), lineWidth: 2)
+            }
+            
+            if showingIndicators {
+                IndicatorPoint()
+                    .position(x: IndicatorPointPosition.x, y: IndicatorPointPosition.y)
+            }
         }
+        .rotationEffect(.degrees(180), anchor: .center)
+        .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
+        .contentShape(Rectangle())  // Control tappable area
+        .gesture(
+            LongPressGesture(minimumDuration: 0.2)
+                .sequenced(before: DragGesture(minimumDistance: 0, coordinateSpace: .local))
+                .onChanged({ value in  // Get value of the gesture
+                    switch value {
+                    case .second(true, let drag):
+                        if let longPressLocation = drag?.location {
+                            dragGesture(longPressLocation)
+                        }
+                    default:
+                        break
+                    }
+                })
+                // Hide indicator when finish
+                .onEnded({ value in
+                    self.showingIndicators = false
+                })
+        )
     }
     
     /*
@@ -30,7 +63,41 @@
         } else if data.first! == data.last! {
             color = Color(.systemTeal)
         }
+        else if showingIndicators {
+            color = Color(.systemBlue)
+        }
         
         return color
     }
+    
+    /*
+     When the user drag on Path -> Modifiy indicator point to move it on the path accordingly
+     */
+    private func dragGesture(_ longPressLocation: CGPoint) {
+        let (closestXPoint, closestYPoint, yPointIndex) = getClosestValueFrom(longPressLocation, inData: pathPoints)
+        self.IndicatorPointPosition.x = closestXPoint
+        self.IndicatorPointPosition.y = closestYPoint
+        self.showingIndicators = true
+        self.indexPosition = yPointIndex
+    }
+    
+    /*
+     First, search the closest X point in Path from the tapped location.
+     Then, find the correspondent Y point in Path.
+     */
+    private func getClosestValueFrom(_ value: CGPoint, inData: [CGPoint]) -> (CGFloat, CGFloat, Int) {
+        let touchPoint: (CGFloat, CGFloat) = (value.x, value.y)
+        let xPathPoints = inData.map { $0.x }
+        let yPathPoints = inData.map { $0.y }
+        
+        // Closest X value
+        let closestXPoint = xPathPoints.enumerated().min( by: { abs($0.1 - touchPoint.0) < abs($1.1 - touchPoint.0) } )!
+        let closestYPointIndex = xPathPoints.firstIndex(of: closestXPoint.element)!
+        let closestYPoint = yPathPoints[closestYPointIndex]
+        
+        // Index of the closest points in the array
+        let yPointIndex = yPathPoints.firstIndex(of: closestYPoint)!
+
+        return (closestXPoint.element, closestYPoint, yPointIndex)
+    }
 }