Skip to content

Commit caa75ec

Browse files
authored
feat: add linechart interaction point (#202)
* feat: add linechart interaction point * feat: add ability to show current data point on linechart
1 parent 7861bbc commit caa75ec

6 files changed

Lines changed: 59 additions & 27 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>SwiftUICharts.xcscheme_^#shared#^_</key>
8+
<dict>
9+
<key>orderHint</key>
10+
<integer>2</integer>
11+
</dict>
12+
</dict>
13+
</dict>
14+
</plist>

Sources/SwiftUICharts/Base/CardView/CardView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ public struct CardView<Content: View>: View, ChartBase {
2626
if showShadow {
2727
RoundedRectangle(cornerRadius: 20)
2828
.fill(Color.white)
29-
.shadow(color: Color.gray, radius: 8)
29+
.shadow(color: Color(white: 0.9, opacity: 1), radius: 8)
3030
}
31-
VStack {
31+
VStack (alignment: .leading) {
3232
self.content()
3333
}
3434
.clipShape(RoundedRectangle(cornerRadius: showShadow ? 20 : 0))

Sources/SwiftUICharts/Base/Chart/ChartData.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ public class ChartData: ObservableObject {
1313
}
1414

1515
var normalisedPoints: [Double] {
16-
points.map { $0 / (points.max() ?? 1.0) }
16+
let absolutePoints = points.map { abs($0) }
17+
return points.map { $0 / (absolutePoints.max() ?? 1.0) }
1718
}
1819

1920
var normalisedRange: Double {

Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,10 @@ extension CGPoint {
3838

3939
return CGPoint(x: stepWidth, y: stepHeight)
4040
}
41+
42+
func denormalize(with geometry: GeometryProxy) -> CGPoint {
43+
let width = geometry.frame(in: .local).width
44+
let height = geometry.frame(in: .local).height
45+
return CGPoint(x: self.x * width, y: self.y * height)
46+
}
4147
}

Sources/SwiftUICharts/Base/Grid/ChartGrid.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ struct DashedLine: View {
4646
var body: some View {
4747
GeometryReader { geometry in
4848
line(frame: geometry.frame(in: .local))
49-
.stroke(Color(white: 0.3), style: StrokeStyle(lineWidth: 1, lineCap: .round, dash: [5, 10]))
49+
.stroke(Color(white: 0.85), style: StrokeStyle(lineWidth: 1, lineCap: .round, dash: [5, 10]))
5050
}
5151
}
5252
}

Sources/SwiftUICharts/Charts/LineChart/Line.swift

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ public struct Line: View {
1313
@State private var didCellAppear: Bool = false
1414

1515
var curvedLines: Bool = true
16+
var path: Path {
17+
Path.quadCurvedPathWithPoints(points: chartData.normalisedPoints,
18+
step: CGPoint(x: 1.0, y: 1.0))
19+
}
1620

1721
/// The content and behavior of the `Line`.
1822
/// 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.
@@ -31,12 +35,13 @@ public struct Line: View {
3135
style: style,
3236
trimTo: didCellAppear ? 1.0 : 0.0)
3337
.animation(.easeIn)
34-
// if self.showIndicator {
35-
// IndicatorPoint()
36-
// .position(self.getClosestPointOnPath(touchLocation: self.touchLocation))
37-
// .rotationEffect(.degrees(180), anchor: .center)
38-
// .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
39-
// }
38+
if self.showIndicator {
39+
IndicatorPoint()
40+
.position(self.getClosestPointOnPath(geometry: geometry,
41+
touchLocation: self.touchLocation))
42+
.rotationEffect(.degrees(180), anchor: .center)
43+
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
44+
}
4045
}
4146
.onAppear {
4247
didCellAppear = true
@@ -49,7 +54,7 @@ public struct Line: View {
4954
.onChanged({ value in
5055
self.touchLocation = value.location
5156
self.showIndicator = true
52-
// self.getClosestDataPoint(point: self.getClosestPointOnPath(touchLocation: value.location))
57+
self.getClosestDataPoint(geometry: geometry, touchLocation: value.location)
5358
self.chartValue.interactionInProgress = true
5459
})
5560
.onEnded({ value in
@@ -64,24 +69,30 @@ public struct Line: View {
6469

6570
// MARK: - Private functions
6671

67-
//extension Line {
68-
// /// Calculate point closest to where the user touched
69-
// /// - Parameter touchLocation: location in view where touched
70-
// /// - Returns: `CGPoint` of data point on chart
71-
// private func getClosestPointOnPath(touchLocation: CGPoint) -> CGPoint {
72-
// let closest = self.path.point(to: touchLocation.x)
73-
// return closest
74-
// }
75-
//
72+
extension Line {
73+
/// Calculate point closest to where the user touched
74+
/// - Parameter touchLocation: location in view where touched
75+
/// - Returns: `CGPoint` of data point on chart
76+
private func getClosestPointOnPath(geometry: GeometryProxy, touchLocation: CGPoint) -> CGPoint {
77+
let geometryWidth = geometry.frame(in: .local).width
78+
let normalisedTouchLocationX = (touchLocation.x / geometryWidth) * CGFloat(chartData.normalisedPoints.count - 1)
79+
let closest = self.path.point(to: normalisedTouchLocationX)
80+
var denormClosest = closest.denormalize(with: geometry)
81+
denormClosest.x = denormClosest.x / CGFloat(chartData.normalisedPoints.count - 1)
82+
denormClosest.y = denormClosest.y / CGFloat(chartData.normalisedRange)
83+
return denormClosest
84+
}
85+
7686
// /// Figure out where closest touch point was
7787
// /// - Parameter point: location of data point on graph, near touch location
78-
// private func getClosestDataPoint(point: CGPoint) {
79-
// let index = Int(round((point.x)/step.x))
80-
// if (index >= 0 && index < self.chartData.data.count){
81-
// self.chartValue.currentValue = self.chartData.points[index]
82-
// }
83-
// }
84-
//}
88+
private func getClosestDataPoint(geometry: GeometryProxy, touchLocation: CGPoint) {
89+
let geometryWidth = geometry.frame(in: .local).width
90+
let index = Int(round((touchLocation.x / geometryWidth) * CGFloat(chartData.points.count - 1)))
91+
if (index >= 0 && index < self.chartData.data.count){
92+
self.chartValue.currentValue = self.chartData.points[index]
93+
}
94+
}
95+
}
8596

8697
struct Line_Previews: PreviewProvider {
8798
/// Predefined style, black over white, for preview

0 commit comments

Comments
 (0)