-
Notifications
You must be signed in to change notification settings - Fork 644
Expand file tree
/
Copy pathLine.swift
More file actions
112 lines (99 loc) · 4.78 KB
/
Line.swift
File metadata and controls
112 lines (99 loc) · 4.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import SwiftUI
/// A single line of data, a view in a `LineChart`
public struct Line: View {
@EnvironmentObject var chartValue: ChartValue
@ObservedObject var chartData: ChartData
var style: ChartStyle
@State private var showIndicator: Bool = false
@State private var touchLocation: CGPoint = .zero
@State private var showBackground: Bool = true
@State private var didCellAppear: Bool = false
var curvedLines: Bool = true
var path: Path {
Path.quadCurvedPathWithPoints(points: chartData.normalisedPoints,
step: CGPoint(x: 1.0, y: 1.0))
}
/// The content and behavior of the `Line`.
/// Draw the background if showing the full line (?) and the `showBackground` option is set. Above that draw the line, and then the data indicator if the graph is currently being touched.
/// On appear, set the frame so that the data graph metrics can be calculated. On a drag (touch) gesture, highlight the closest touched data point.
/// TODO: explain rotation
public var body: some View {
GeometryReader { geometry in
ZStack {
if self.didCellAppear && self.showBackground {
LineBackgroundShapeView(chartData: chartData,
geometry: geometry,
style: style)
.animation(.easeIn)
}
LineShapeView(chartData: chartData,
geometry: geometry,
style: style,
trimTo: didCellAppear ? 1.0 : 0.0)
.animation(.easeIn)
if self.showIndicator {
IndicatorPoint()
.position(self.getClosestPointOnPath(geometry: geometry,
touchLocation: self.touchLocation))
.rotationEffect(.degrees(180), anchor: .center)
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
}
}
.onAppear {
didCellAppear = true
}
.onDisappear() {
didCellAppear = false
}
.gesture(DragGesture()
.onChanged({ value in
self.touchLocation = value.location
self.showIndicator = true
self.getClosestDataPoint(geometry: geometry, touchLocation: value.location)
self.chartValue.interactionInProgress = true
})
.onEnded({ value in
self.touchLocation = .zero
self.showIndicator = false
self.chartValue.interactionInProgress = false
})
)
}
}
}
// MARK: - Private functions
extension Line {
/// Calculate point closest to where the user touched
/// - Parameter touchLocation: location in view where touched
/// - Returns: `CGPoint` of data point on chart
private func getClosestPointOnPath(geometry: GeometryProxy, touchLocation: CGPoint) -> CGPoint {
let geometryWidth = geometry.frame(in: .local).width
let normalisedTouchLocationX = (touchLocation.x / geometryWidth) * CGFloat(chartData.normalisedPoints.count - 1)
let closest = self.path.point(to: normalisedTouchLocationX)
var denormClosest = closest.denormalize(with: geometry)
denormClosest.x = denormClosest.x / CGFloat(chartData.normalisedPoints.count - 1)
denormClosest.y = denormClosest.y / CGFloat(chartData.normalisedRange)
return denormClosest
}
// /// Figure out where closest touch point was
// /// - Parameter point: location of data point on graph, near touch location
private func getClosestDataPoint(geometry: GeometryProxy, touchLocation: CGPoint) {
let geometryWidth = geometry.frame(in: .local).width
let index = Int(round((touchLocation.x / geometryWidth) * CGFloat(chartData.points.count - 1)))
if (index >= 0 && index < self.chartData.data.count){
self.chartValue.currentValue = self.chartData.points[index]
}
}
}
struct Line_Previews: PreviewProvider {
/// Predefined style, black over white, for preview
static let blackLineStyle = ChartStyle(backgroundColor: ColorGradient(.white), foregroundColor: ColorGradient(.black))
/// Predefined style red over white, for preview
static let redLineStyle = ChartStyle(backgroundColor: .whiteBlack, foregroundColor: ColorGradient(.red))
static var previews: some View {
Group {
Line(chartData: ChartData([8, 23, 32, 7, 23, -4]), style: blackLineStyle)
Line(chartData: ChartData([8, 23, 32, 7, 23, 43]), style: redLineStyle)
}
}
}