changeset 274:61208d7aa715

Implementing LineChart drag animation
author Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com>
date Wed, 17 Mar 2021 20:26:19 +0100
parents 39428219f832
children 62f2c675b666
files LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate LazyBear/UI/LineChart.swift LazyBear/UI/LineView.swift
diffstat 3 files changed, 46 insertions(+), 28 deletions(-) [+]
line wrap: on
line diff
Binary file LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate has changed
--- a/LazyBear/UI/LineChart.swift	Wed Mar 17 17:01:13 2021 +0100
+++ b/LazyBear/UI/LineChart.swift	Wed Mar 17 20:26:19 2021 +0100
@@ -17,6 +17,9 @@
             
             VStack {
                 LineView(width: deviceSize.width, height: deviceSize.width / 3, normalizedData: normalizedData)
+                    .rotationEffect(.degrees(180), anchor: .center) 
+                    .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
+                    
             }
             .frame(width: deviceSize.width, height: deviceSize.width / 3)
         }
--- a/LazyBear/UI/LineView.swift	Wed Mar 17 17:01:13 2021 +0100
+++ b/LazyBear/UI/LineView.swift	Wed Mar 17 20:26:19 2021 +0100
@@ -16,41 +16,56 @@
     @State private var touchLocation: CGPoint = .zero
 
     var body: some View {
-        Line(width: width, height: height, normalizedData: normalizedData)
-            .stroke(Color.green, lineWidth: 2)
-            .rotationEffect(.degrees(180), anchor: .center)  // The path must be rotated
-            .rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
-            .gesture(DragGesture()  // Add gesture
-            .onChanged({ value in  // Take value of the gesture
-                print("Location - > \(value.location)")
-            })
-        )
-    }
-}
-
-struct Line: Shape {
-    var width: CGFloat
-    var height: CGFloat
-    var normalizedData: [Double]
-    
-    func path(in rect: CGRect) -> Path {
-        var path = Path()
-        
         // Substract 2 to skip the first and the last item
         let widthBetweenPoints = Double(width) / Double(normalizedData.count - 2)
         let initialPoint = normalizedData[0] * Double(height)
         var x: Double = 0
+        var pathPoints = [CGPoint]()
         
-        path.move(to: CGPoint(x: x, y: initialPoint))
-        for y in normalizedData {
-            // Skip first item
-            if normalizedData.firstIndex(of: y) != 0 {
-                x += widthBetweenPoints
-                let y = y * Double(height)
-                path.addLine(to: CGPoint(x: x, y: y))
+        ZStack {
+            GeometryReader { geo in
+            Path { path in
+                path.move(to: CGPoint(x: x, y: initialPoint))
+                for y in normalizedData {
+                    // Skip first item
+                    if normalizedData.firstIndex(of: y) != 0 {
+                        x += widthBetweenPoints
+                        let y = y * Double(height)
+                        path.addLine(to: CGPoint(x: x, y: y))
+                    }
+                    
+                    pathPoints.append(path.currentPoint!)
+                }
+            }
+            .stroke(Color.green, lineWidth: 2)
+            .gesture(DragGesture()  // Add gesture
+            .onChanged({ value in  // Take value of the gesture
+                let (closestXPoint, closestYPoint) = getClosestValueFrom(value.location, inData: pathPoints)
+                self.touchLocation.x = closestXPoint
+                self.touchLocation.y = closestYPoint
+                
+            }))
+            
+            
+            IndicatorPoint()
+                .position(x: touchLocation.x, y: touchLocation.y)
             }
         }
-        return path
+    }
+    
+    // First search the closest X path point from the touch location. The find the correspondant Y path point
+    // given the X path point
+    func getClosestValueFrom(_ value: CGPoint, inData: [CGPoint]) -> (CGFloat, CGFloat) {
+        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]
+        
+        return (closestXPoint.element, closestYPoint)
     }
 }