diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/samuandris.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/samuandris.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..a5f64f0a Binary files /dev/null and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/samuandris.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/.swiftpm/xcode/xcuserdata/samuandris.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/.swiftpm/xcode/xcuserdata/samuandris.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist new file mode 100644 index 00000000..867e4fe8 --- /dev/null +++ b/.swiftpm/xcode/xcuserdata/samuandris.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -0,0 +1,6 @@ + + + diff --git a/.swiftpm/xcode/xcuserdata/samuandris.xcuserdatad/xcschemes/xcschememanagement.plist b/.swiftpm/xcode/xcuserdata/samuandris.xcuserdatad/xcschemes/xcschememanagement.plist index a0f26bbb..1be8e537 100644 --- a/.swiftpm/xcode/xcuserdata/samuandris.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/.swiftpm/xcode/xcuserdata/samuandris.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,20 @@ SwiftUICharts.xcscheme_^#shared#^_ orderHint - 2 + 0 + + + SuppressBuildableAutocreation + + SwiftUICharts + + primary + + + SwiftUIChartsTests + + primary + diff --git a/Sources/SwiftUICharts/Base/Chart/ChartData.swift b/Sources/SwiftUICharts/Base/Chart/ChartData.swift index 28c2065d..b598975c 100644 --- a/Sources/SwiftUICharts/Base/Chart/ChartData.swift +++ b/Sources/SwiftUICharts/Base/Chart/ChartData.swift @@ -12,8 +12,20 @@ public class ChartData: ObservableObject { data.map { $0.0 } } - /// Initialize with data array - /// - Parameter data: Array of `Double` + var normalisedPoints: [Double] { + points.map { $0 / (points.max() ?? 1.0) } + } + + var normalisedRange: Double { + (normalisedPoints.max() ?? 0.0) - (normalisedPoints.min() ?? 0.0) + } + + var isInNegativeDomain: Bool { + (points.min() ?? 0.0) < 0 + } + + /// Initialize with data array + /// - Parameter data: Array of `Double` public init(_ data: [Double]) { self.data = data.map { ("", $0) } } diff --git a/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift b/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift index a9a9b086..363cd2c3 100644 --- a/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift +++ b/Sources/SwiftUICharts/Base/Extensions/CGPoint+Extension.swift @@ -8,7 +8,7 @@ extension CGPoint { /// - data: array of `Double` /// - Returns: X and Y delta as a `CGPoint` static func getStep(frame: CGRect, data: [Double]) -> CGPoint { - let padding: CGFloat = 30.0 + let padding: CGFloat = 0 // stepWidth var stepWidth: CGFloat = 0.0 diff --git a/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift b/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift index 4562b37f..ecaf270b 100644 --- a/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift +++ b/Sources/SwiftUICharts/Base/Grid/ChartGrid.swift @@ -1,25 +1,52 @@ import SwiftUI -/// <#Description#> public struct ChartGrid: View, ChartBase { public var chartData = ChartData() let content: () -> Content + let numberOfHorizontalLines = 4 @EnvironmentObject var data: ChartData @EnvironmentObject var style: ChartStyle - /// <#Description#> - /// - Parameter content: <#content description#> public init(@ViewBuilder content: @escaping () -> Content) { self.content = content } - /// The content and behavior of the `ChartGrid`. - /// - /// TODO: Explain why this is in a `ZStack` public var body: some View { - ZStack{ - self.content() + HStack { + ZStack { + VStack { + ForEach(0.. Path { + let baseLine: CGFloat = CGFloat(frame.height / 2) + var hLine = Path() + hLine.move(to: CGPoint(x:0, y: baseLine)) + hLine.addLine(to: CGPoint(x: frame.width, y: baseLine)) + return hLine + } + + 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])) } } } diff --git a/Sources/SwiftUICharts/Charts/BarChart/BarChartCell.swift b/Sources/SwiftUICharts/Charts/BarChart/BarChartCell.swift index 761add15..e457d3cd 100644 --- a/Sources/SwiftUICharts/Charts/BarChart/BarChartCell.swift +++ b/Sources/SwiftUICharts/Charts/BarChart/BarChartCell.swift @@ -4,27 +4,17 @@ import SwiftUI public struct BarChartCell: View { var value: Double var index: Int = 0 - var width: Float - var numberOfDataPoints: Int var gradientColor: ColorGradient var touchLocation: CGFloat - var cellWidth: Double { - return Double(width)/(Double(numberOfDataPoints) * 1.5) - } - - @State private var firstDisplay: Bool = true + @State private var didCellAppear: Bool = false public init( value: Double, index: Int = 0, - width: Float, - numberOfDataPoints: Int, gradientColor: ColorGradient, touchLocation: CGFloat) { self.value = value self.index = index - self.width = width - self.numberOfDataPoints = numberOfDataPoints self.gradientColor = gradientColor self.touchLocation = touchLocation } @@ -33,20 +23,15 @@ public struct BarChartCell: View { /// /// Animated when first displayed, using the `firstDisplay` variable, with an increasing delay through the data set. public var body: some View { - ZStack { - RoundedRectangle(cornerRadius: 4) - .fill(gradientColor.linearGradient(from: .bottom, to: .top)) - } - .frame(width: CGFloat(self.cellWidth)) - .scaleEffect(CGSize(width: 1, height: self.firstDisplay ? 0.0 : self.value), anchor: .bottom) - .onAppear { - self.firstDisplay = false + BarChartCellShape(value: didCellAppear ? value : 0.0) + .fill(gradientColor.linearGradient(from: .bottom, to: .top)) .onAppear { + self.didCellAppear = true } .onDisappear { - self.firstDisplay = true + self.didCellAppear = false } .transition(.slide) - .animation(Animation.spring().delay(self.touchLocation < 0 || !firstDisplay ? Double(self.index) * 0.04 : 0)) + .animation(Animation.spring().delay(self.touchLocation < 0 || !didCellAppear ? Double(self.index) * 0.04 : 0)) } } @@ -54,17 +39,17 @@ struct BarChartCell_Previews: PreviewProvider { static var previews: some View { Group { Group { - BarChartCell(value: 0, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat()) + BarChartCell(value: 0, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat()) - BarChartCell(value: 1, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat()) - BarChartCell(value: 1, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient.whiteBlack, touchLocation: CGFloat()) - BarChartCell(value: 1, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient(.purple), touchLocation: CGFloat()) + BarChartCell(value: 0.5, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat()) + BarChartCell(value: 0.75, gradientColor: ColorGradient.whiteBlack, touchLocation: CGFloat()) + BarChartCell(value: 1, gradientColor: ColorGradient(.purple), touchLocation: CGFloat()) } Group { - BarChartCell(value: 1, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat()) - BarChartCell(value: 1, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient.whiteBlack, touchLocation: CGFloat()) - BarChartCell(value: 1, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient(.purple), touchLocation: CGFloat()) + BarChartCell(value: 1, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat()) + BarChartCell(value: 1, gradientColor: ColorGradient.whiteBlack, touchLocation: CGFloat()) + BarChartCell(value: 1, gradientColor: ColorGradient(.purple), touchLocation: CGFloat()) }.environment(\.colorScheme, .dark) } } diff --git a/Sources/SwiftUICharts/Charts/BarChart/BarChartCellShape.swift b/Sources/SwiftUICharts/Charts/BarChart/BarChartCellShape.swift new file mode 100644 index 00000000..5cf99839 --- /dev/null +++ b/Sources/SwiftUICharts/Charts/BarChart/BarChartCellShape.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct BarChartCellShape: Shape, Animatable { + var value: Double + var cornerRadius: CGFloat = 6.0 + var animatableData: CGFloat { + get { CGFloat(value) } + set { value = Double(newValue) } + } + + func path(in rect: CGRect) -> Path { + let adjustedOriginY = rect.height - (rect.height * CGFloat(value)) + var path = Path() + path.move(to: CGPoint(x: 0.0 , y: rect.height)) + path.addLine(to: CGPoint(x: 0.0, y: adjustedOriginY + cornerRadius)) + path.addArc(center: CGPoint(x: cornerRadius, y: adjustedOriginY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(radians: Double.pi), + endAngle: Angle(radians: -Double.pi/2), + clockwise: false) + path.addLine(to: CGPoint(x: rect.width - cornerRadius, y: adjustedOriginY)) + path.addArc(center: CGPoint(x: rect.width - cornerRadius, y: adjustedOriginY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(radians: -Double.pi/2), + endAngle: Angle(radians: 0), + clockwise: false) + path.addLine(to: CGPoint(x: rect.width, y: rect.height)) + path.closeSubpath() + + return path + } +} + +struct BarChartCellShape_Previews: PreviewProvider { + static var previews: some View { + Group { + BarChartCellShape(value: 0.75) + .fill(Color.red) + + BarChartCellShape(value: 0.3) + .fill(Color.blue) + } + } +} diff --git a/Sources/SwiftUICharts/Charts/BarChart/BarChartRow.swift b/Sources/SwiftUICharts/Charts/BarChart/BarChartRow.swift index 366a3319..5cfd64b9 100644 --- a/Sources/SwiftUICharts/Charts/BarChart/BarChartRow.swift +++ b/Sources/SwiftUICharts/Charts/BarChart/BarChartRow.swift @@ -6,10 +6,6 @@ public struct BarChartRow: View { @ObservedObject var chartData: ChartData @State private var touchLocation: CGFloat = -1.0 - enum Constant { - static let spacing: CGFloat = 16.0 - } - var style: ChartStyle var maxValue: Double { @@ -27,20 +23,18 @@ public struct BarChartRow: View { public var body: some View { GeometryReader { geometry in HStack(alignment: .bottom, - spacing: (geometry.frame(in: .local).width - Constant.spacing) / CGFloat(self.chartData.data.count * 3)) { - ForEach(0.. Double { - return Double(chartData.points[index])/Double(maxValue) - } - /// Size to scale the touch indicator /// - Parameters: /// - touchLocation: fraction of width where touch is happening @@ -87,3 +74,11 @@ public struct BarChartRow: View { return self.chartData.points[index] } } + +struct BarChartRow_Previews: PreviewProvider { + static let chartData = ChartData([6, 2, 5, 8, 6]) + static let chartStyle = ChartStyle(backgroundColor: .white, foregroundColor: .orangeBright) + static var previews: some View { + BarChartRow(chartData: chartData, style: chartStyle) + } +} diff --git a/Sources/SwiftUICharts/Charts/LineChart/Line.swift b/Sources/SwiftUICharts/Charts/LineChart/Line.swift index 942342de..0e9fa34f 100644 --- a/Sources/SwiftUICharts/Charts/LineChart/Line.swift +++ b/Sources/SwiftUICharts/Charts/LineChart/Line.swift @@ -3,57 +3,16 @@ import SwiftUI /// A single line of data, a view in a `LineChart` public struct Line: View { @EnvironmentObject var chartValue: ChartValue - @State private var frame: CGRect = .zero @ObservedObject var chartData: ChartData var style: ChartStyle @State private var showIndicator: Bool = false @State private var touchLocation: CGPoint = .zero - @State private var showFull: Bool = false @State private var showBackground: Bool = true - var curvedLines: Bool = true - - /// Step for plotting through data - /// - Returns: X and Y delta between each data point based on data and view's frame - var step: CGPoint { - return CGPoint.getStep(frame: frame, data: chartData.points) - } - - /// Path of line graph - /// - Returns: A path for stroking representing the data, either curved or jagged. - var path: Path { - let points = chartData.points - - if curvedLines { - return Path.quadCurvedPathWithPoints(points: points, - step: step, - globalOffset: nil) - } - - return Path.linePathWithPoints(points: points, step: step) - } - - /// Path of linegraph, but also closed at the bottom side - /// - Returns: A path for filling representing the data, either curved or jagged - var closedPath: Path { - let points = chartData.points - - if curvedLines { - return Path.quadClosedCurvedPathWithPoints(points: points, - step: step, - globalOffset: nil) - } - - return Path.closedLinePathWithPoints(points: points, step: step) - } + @State private var didCellAppear: Bool = false - // see https://stackoverflow.com/a/62370919 - // This lets geometry be recalculated when device rotates. However it doesn't cover issue of app changing - // from full screen to split view. Not possible in SwiftUI? Feedback submitted to apple FB8451194. - let orientationChanged = NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification) - .makeConnectable() - .autoconnect() + var curvedLines: Bool = true /// 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. @@ -62,34 +21,35 @@ public struct Line: View { public var body: some View { GeometryReader { geometry in ZStack { - if self.showFull && self.showBackground { - self.getBackgroundPathView() - } - self.getLinePathView() - 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.didCellAppear && self.showBackground { + LineBackgroundShapeView(chartData: chartData, + geometry: geometry, + style: style) } + LineShapeView(chartData: chartData, + geometry: geometry, + 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)) +// } } .onAppear { - self.frame = geometry.frame(in: .local) - + didCellAppear = true + } + .onDisappear() { + didCellAppear = false } - .onReceive(orientationChanged) { _ in - // When we receive notification here, the geometry is still the old value - // so delay evaluation to get the new frame! - DispatchQueue.main.async { - self.frame = geometry.frame(in: .local) // recalculate layout with new frame - } - } .gesture(DragGesture() .onChanged({ value in self.touchLocation = value.location self.showIndicator = true - self.getClosestDataPoint(point: self.getClosestPointOnPath(touchLocation: value.location)) +// self.getClosestDataPoint(point: self.getClosestPointOnPath(touchLocation: value.location)) self.chartValue.interactionInProgress = true }) .onEnded({ value in @@ -104,80 +64,37 @@ 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(touchLocation: CGPoint) -> CGPoint { +// let closest = self.path.point(to: touchLocation.x) +// return closest +// } +// +// /// 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] +// } +// } +//} - /// 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] - } - } +struct Line_Previews: PreviewProvider { + /// Predefined style, black over white, for preview + static let blackLineStyle = ChartStyle(backgroundColor: ColorGradient(.white), foregroundColor: ColorGradient(.black)) - /// Get the view representing the filled in background below the chart, filled with the foreground color's gradient - /// - /// TODO: explain rotations - /// - Returns: SwiftUI `View` - private func getBackgroundPathView() -> some View { - self.closedPath - .fill(LinearGradient(gradient: Gradient(colors: [ - style.foregroundColor.first?.startColor ?? .white, - style.foregroundColor.first?.endColor ?? .white, - .clear]), - startPoint: .bottom, - endPoint: .top)) - .rotationEffect(.degrees(180), anchor: .center) - .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) - .opacity(0.2) - .transition(.opacity) - .animation(.easeIn(duration: 1.6)) - } + /// Predefined style red over white, for preview + static let redLineStyle = ChartStyle(backgroundColor: .whiteBlack, foregroundColor: ColorGradient(.red)) - /// Get the view representing the line stroked in the `foregroundColor` - /// - /// TODO: Explain how `showFull` works - /// TODO: explain rotations - /// - Returns: SwiftUI `View` - private func getLinePathView() -> some View { - self.path - .trim(from: 0, to: self.showFull ? 1:0) - .stroke(LinearGradient(gradient: style.foregroundColor.first?.gradient ?? ColorGradient.orangeBright.gradient, - startPoint: .leading, - endPoint: .trailing), - style: StrokeStyle(lineWidth: 3, lineJoin: .round)) - .rotationEffect(.degrees(180), anchor: .center) - .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) - .animation(Animation.easeOut(duration: 1.2)) - .onAppear { - self.showFull = true - } - .onDisappear { - self.showFull = false - } - .drawingGroup() - } -} - -struct Line_Previews: PreviewProvider { static var previews: some View { Group { - Line(chartData: ChartData([8, 23, 32, 7, 23, 43]), style: blackLineStyle) + Line(chartData: ChartData([8, 23, 32, 7, 23, -4]), style: blackLineStyle) Line(chartData: ChartData([8, 23, 32, 7, 23, 43]), style: redLineStyle) } } } -/// Predefined style, black over white, for preview -private let blackLineStyle = ChartStyle(backgroundColor: ColorGradient(.white), foregroundColor: ColorGradient(.black)) - -/// Predefined stylem red over white, for preview -private let redLineStyle = ChartStyle(backgroundColor: .whiteBlack, foregroundColor: ColorGradient(.red)) diff --git a/Sources/SwiftUICharts/Charts/LineChart/LineBackgroundShape.swift b/Sources/SwiftUICharts/Charts/LineChart/LineBackgroundShape.swift new file mode 100644 index 00000000..06b02811 --- /dev/null +++ b/Sources/SwiftUICharts/Charts/LineChart/LineBackgroundShape.swift @@ -0,0 +1,31 @@ +import SwiftUI + +struct LineBackgroundShape: Shape { + var data: [Double] + func path(in rect: CGRect) -> Path { + let path = Path.quadClosedCurvedPathWithPoints(points: data, step: CGPoint(x: 1.0, y: 1.0)) + return path + } +} + +struct LineBackgroundShape_Previews: PreviewProvider { + static var previews: some View { + Group { + GeometryReader { geometry in + LineBackgroundShape(data: [0, 0.5, 0.8, 0.6, 1]) + .transform(CGAffineTransform(scaleX: geometry.size.width / 4.0, y: geometry.size.height)) + .fill(Color.red) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } + GeometryReader { geometry in + LineBackgroundShape(data: [0, -0.5, 0.8, -0.6, 1]) + .transform(CGAffineTransform(scaleX: geometry.size.width / 4.0, y: geometry.size.height / 1.6)) + .fill(Color.blue) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } + } + } +} + diff --git a/Sources/SwiftUICharts/Charts/LineChart/LineBackgroundShapeView.swift b/Sources/SwiftUICharts/Charts/LineChart/LineBackgroundShapeView.swift new file mode 100644 index 00000000..25ba8214 --- /dev/null +++ b/Sources/SwiftUICharts/Charts/LineChart/LineBackgroundShapeView.swift @@ -0,0 +1,19 @@ +import SwiftUI + +struct LineBackgroundShapeView: View { + var chartData: ChartData + var geometry: GeometryProxy + var style: ChartStyle + + var body: some View { + LineBackgroundShape(data: chartData.normalisedPoints) + .transform(CGAffineTransform(scaleX: geometry.size.width / CGFloat(chartData.normalisedPoints.count - 1), + y: geometry.size.height / CGFloat(chartData.normalisedRange))) + .fill(LinearGradient(gradient: Gradient(colors: [style.foregroundColor.first?.startColor ?? .white, + style.backgroundColor.startColor]), + startPoint: .bottom, + endPoint: .top)) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } +} diff --git a/Sources/SwiftUICharts/Charts/LineChart/LineShape.swift b/Sources/SwiftUICharts/Charts/LineChart/LineShape.swift new file mode 100644 index 00000000..64fff658 --- /dev/null +++ b/Sources/SwiftUICharts/Charts/LineChart/LineShape.swift @@ -0,0 +1,30 @@ +import SwiftUI + +struct LineShape: Shape { + var data: [Double] + func path(in rect: CGRect) -> Path { + let path = Path.quadCurvedPathWithPoints(points: data, step: CGPoint(x: 1.0, y: 1.0)) + return path + } +} + +struct LineShape_Previews: PreviewProvider { + static var previews: some View { + Group { + GeometryReader { geometry in + LineShape(data: [0, 0.5, 0.8, 0.6, 1]) + .transform(CGAffineTransform(scaleX: geometry.size.width / 4.0, y: geometry.size.height)) + .stroke(Color.red) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } + GeometryReader { geometry in + LineShape(data: [0, -0.5, 0.8, -0.6, 1]) + .transform(CGAffineTransform(scaleX: geometry.size.width / 4.0, y: geometry.size.height / 1.6)) + .stroke(Color.blue) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } + } + } +} diff --git a/Sources/SwiftUICharts/Charts/LineChart/LineShapeView.swift b/Sources/SwiftUICharts/Charts/LineChart/LineShapeView.swift new file mode 100644 index 00000000..d7143932 --- /dev/null +++ b/Sources/SwiftUICharts/Charts/LineChart/LineShapeView.swift @@ -0,0 +1,26 @@ +import SwiftUI + +struct LineShapeView: View, Animatable { + var chartData: ChartData + var geometry: GeometryProxy + var style: ChartStyle + var trimTo: Double = 0 + + var animatableData: CGFloat { + get { CGFloat(trimTo) } + set { trimTo = Double(newValue) } + } + + var body: some View { + LineShape(data: chartData.normalisedPoints) + .trim(from: 0, to: CGFloat(trimTo)) + .transform(CGAffineTransform(scaleX: geometry.size.width / CGFloat(chartData.normalisedPoints.count - 1), + y: geometry.size.height / CGFloat(chartData.normalisedRange))) + .stroke(LinearGradient(gradient: style.foregroundColor.first?.gradient ?? ColorGradient.orangeBright.gradient, + startPoint: .leading, + endPoint: .trailing), + style: StrokeStyle(lineWidth: 3, lineJoin: .round)) + .rotationEffect(.degrees(180), anchor: .center) + .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0)) + } +} diff --git a/Tests/SwiftUIChartsTests/ArrayExtensionTests.swift b/Tests/SwiftUIChartsTests/ArrayExtensionTests.swift index ad77114c..f76c00e2 100644 --- a/Tests/SwiftUIChartsTests/ArrayExtensionTests.swift +++ b/Tests/SwiftUIChartsTests/ArrayExtensionTests.swift @@ -1,10 +1,3 @@ -// -// File.swift -// -// -// Created by Nicolas Savoini on 2020-05-25. -// - @testable import SwiftUICharts import XCTest