# HG changeset patch # User Dennis Concepción Martín <66180929+denniscm190@users.noreply.github.com> # Date 1616009179 -3600 # Node ID 61208d7aa71546ea3616030b9de5f1ca32df076f # Parent 39428219f83234834fd498e4cf6b2005ab1faa82 Implementing LineChart drag animation diff -r 39428219f832 -r 61208d7aa715 LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate Binary file LazyBear.xcodeproj/project.xcworkspace/xcuserdata/dennis.xcuserdatad/UserInterfaceState.xcuserstate has changed diff -r 39428219f832 -r 61208d7aa715 LazyBear/UI/LineChart.swift --- 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) } diff -r 39428219f832 -r 61208d7aa715 LazyBear/UI/LineView.swift --- 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) } }