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