From 71b8e458620ad3ea52bc98c4ff71545e35467bce Mon Sep 17 00:00:00 2001 From: Samu Andras Date: Wed, 4 Aug 2021 21:40:21 +0200 Subject: [PATCH 1/2] feat: add linechart interaction point --- .../xcschemes/xcschememanagement.plist | 14 +++++++ .../Base/CardView/CardView.swift | 4 +- .../Base/Extensions/CGPoint+Extension.swift | 6 +++ .../SwiftUICharts/Base/Grid/ChartGrid.swift | 2 +- .../SwiftUICharts/Charts/LineChart/Line.swift | 42 ++++++++++++------- 5 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 .swiftpm/xcode/xcuserdata/samuandras.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.swiftpm/xcode/xcuserdata/samuandras.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/samuandras.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..a0f26bbb --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/samuandras.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + SwiftUICharts.xcscheme_^#shared#^_ + + orderHint + 2 + + + + diff --git a/Sources/SwiftUICharts/Base/CardView/CardView.swift b/Sources/SwiftUICharts/Base/CardView/CardView.swift index 9109a2b3..e5d7eb3a 100644 --- a/Sources/SwiftUICharts/Base/CardView/CardView.swift +++ b/Sources/SwiftUICharts/Base/CardView/CardView.swift @@ -26,9 +26,9 @@ public struct CardView: View, ChartBase { if showShadow { RoundedRectangle(cornerRadius: 20) .fill(Color.white) - .shadow(color: Color.gray, radius: 8) + .shadow(color: Color(white: 0.9, opacity: 1), radius: 8) } - VStack { + VStack (alignment: .leading) { self.content() } .clipShape(RoundedRectangle(cornerRadius: showShadow ? 20 : 0)) diff --git a/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift b/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift index 363cd2c3..31c401d9 100644 --- a/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift +++ b/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift @@ -38,4 +38,10 @@ extension CGPoint { return CGPoint(x: stepWidth, y: stepHeight) } + + func denormalize(with geometry: GeometryProxy) -> CGPoint { + let width = geometry.frame(in: .local).width + let height = geometry.frame(in: .local).height + return CGPoint(x: self.x * width, y: self.y * height) + } } diff --git a/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift b/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift index ecaf270b..eb48a98e 100644 --- a/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift +++ b/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift @@ -46,7 +46,7 @@ struct DashedLine: View { var body: some View { GeometryReader { geometry in line(frame: geometry.frame(in: .local)) - .stroke(Color(white: 0.3), style: StrokeStyle(lineWidth: 1, lineCap: .round, dash: [5, 10])) + .stroke(Color(white: 0.85), style: StrokeStyle(lineWidth: 1, lineCap: .round, dash: [5, 10])) } } } diff --git a/Sources/SwiftUICharts/Charts/LineChart/Line.swift b/Sources/SwiftUICharts/Charts/LineChart/Line.swift index 0e9fa34f..5dc730f7 100644 --- a/Sources/SwiftUICharts/Charts/LineChart/Line.swift +++ b/Sources/SwiftUICharts/Charts/LineChart/Line.swift @@ -13,6 +13,10 @@ public struct Line: View { @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. @@ -31,12 +35,13 @@ public struct Line: View { style: style, trimTo: didCellAppear ? 1.0 : 0.0) .animation(.easeIn) -// if self.showIndicator { -// IndicatorPoint() -// .position(self.getClosestPointOnPath(touchLocation: self.touchLocation)) -// .rotationEffect(.degrees(180), anchor: .center) -// .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) -// } + 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 @@ -64,15 +69,20 @@ public struct Line: View { // 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(touchLocation: CGPoint) -> CGPoint { -// let closest = self.path.point(to: touchLocation.x) -// return closest -// } -// +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 normClosest = closest.denormalize(with: geometry) + normClosest.x = normClosest.x / CGFloat(chartData.normalisedPoints.count - 1) + normClosest.y = normClosest.y * 1.15 + return normClosest + } + // /// Figure out where closest touch point was // /// - Parameter point: location of data point on graph, near touch location // private func getClosestDataPoint(point: CGPoint) { @@ -81,7 +91,7 @@ public struct Line: View { // self.chartValue.currentValue = self.chartData.points[index] // } // } -//} +} struct Line_Previews: PreviewProvider { /// Predefined style, black over white, for preview From 4108f7f9bef57d8f9fca53f82da4784c6a9ff698 Mon Sep 17 00:00:00 2001 From: Samu Andras Date: Wed, 11 Aug 2021 22:01:01 +0200 Subject: [PATCH 2/2] feat: add ability to show current data point on linechart --- .../SwiftUICharts/Base/Chart/ChartData.swift | 3 ++- .../SwiftUICharts/Charts/LineChart/Line.swift | 23 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftUICharts/Base/Chart/ChartData.swift b/Sources/SwiftUICharts/Base/Chart/ChartData.swift index b598975c..17b79ee8 100644 --- a/Sources/SwiftUICharts/Base/Chart/ChartData.swift +++ b/Sources/SwiftUICharts/Base/Chart/ChartData.swift @@ -13,7 +13,8 @@ public class ChartData: ObservableObject { } var normalisedPoints: [Double] { - points.map { $0 / (points.max() ?? 1.0) } + let absolutePoints = points.map { abs($0) } + return points.map { $0 / (absolutePoints.max() ?? 1.0) } } var normalisedRange: Double { diff --git a/Sources/SwiftUICharts/Charts/LineChart/Line.swift b/Sources/SwiftUICharts/Charts/LineChart/Line.swift index 5dc730f7..107367fa 100644 --- a/Sources/SwiftUICharts/Charts/LineChart/Line.swift +++ b/Sources/SwiftUICharts/Charts/LineChart/Line.swift @@ -54,7 +54,7 @@ public struct Line: View { .onChanged({ value in self.touchLocation = value.location self.showIndicator = true -// self.getClosestDataPoint(point: self.getClosestPointOnPath(touchLocation: value.location)) + self.getClosestDataPoint(geometry: geometry, touchLocation: value.location) self.chartValue.interactionInProgress = true }) .onEnded({ value in @@ -77,20 +77,21 @@ extension Line { 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 normClosest = closest.denormalize(with: geometry) - normClosest.x = normClosest.x / CGFloat(chartData.normalisedPoints.count - 1) - normClosest.y = normClosest.y * 1.15 - return normClosest + 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(point: CGPoint) { -// let index = Int(round((point.x)/step.x)) -// if (index >= 0 && index < self.chartData.data.count){ -// self.chartValue.currentValue = self.chartData.points[index] -// } -// } + 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 {