Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 422c5c0303 | |||
| c843e6bede | |||
| 9a588ebe5e |
Generated
BIN
Binary file not shown.
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>SwiftUICharts.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
uuid = "91E23D30-CB6C-44DA-BEFC-9D39A1DA2242"
|
||||
type = "1"
|
||||
version = "2.0">
|
||||
</Bucket>
|
||||
+1
-14
@@ -7,20 +7,7 @@
|
||||
<key>SwiftUICharts.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
<dict>
|
||||
<key>SwiftUICharts</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>SwiftUIChartsTests</key>
|
||||
<dict>
|
||||
<key>primary</key>
|
||||
<true/>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
@@ -26,9 +26,9 @@ public struct CardView<Content: View>: View, ChartBase {
|
||||
if showShadow {
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.fill(Color.white)
|
||||
.shadow(color: Color(white: 0.9, opacity: 1), radius: 8)
|
||||
.shadow(color: Color.gray, radius: 8)
|
||||
}
|
||||
VStack (alignment: .leading) {
|
||||
VStack {
|
||||
self.content()
|
||||
}
|
||||
.clipShape(RoundedRectangle(cornerRadius: showShadow ? 20 : 0))
|
||||
|
||||
@@ -12,21 +12,8 @@ public class ChartData: ObservableObject {
|
||||
data.map { $0.0 }
|
||||
}
|
||||
|
||||
var normalisedPoints: [Double] {
|
||||
let absolutePoints = points.map { abs($0) }
|
||||
return points.map { $0 / (absolutePoints.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`
|
||||
/// Initialize with data array
|
||||
/// - Parameter data: Array of `Double`
|
||||
public init(_ data: [Double]) {
|
||||
self.data = data.map { ("", $0) }
|
||||
}
|
||||
|
||||
@@ -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 = 0
|
||||
let padding: CGFloat = 30.0
|
||||
|
||||
// stepWidth
|
||||
var stepWidth: CGFloat = 0.0
|
||||
@@ -38,10 +38,4 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,52 +1,25 @@
|
||||
import SwiftUI
|
||||
|
||||
/// <#Description#>
|
||||
public struct ChartGrid<Content: View>: 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 {
|
||||
HStack {
|
||||
ZStack {
|
||||
VStack {
|
||||
ForEach(0..<numberOfHorizontalLines) { _ in
|
||||
GridElement()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
self.content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GridElement: View {
|
||||
var body: some View {
|
||||
DashedLine()
|
||||
.frame(maxHeight: 2, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
struct DashedLine: View {
|
||||
func line(frame: CGRect) -> 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.85), style: StrokeStyle(lineWidth: 1, lineCap: .round, dash: [5, 10]))
|
||||
ZStack{
|
||||
self.content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,27 @@ 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
|
||||
|
||||
@State private var didCellAppear: Bool = false
|
||||
var cellWidth: Double {
|
||||
return Double(width)/(Double(numberOfDataPoints) * 1.5)
|
||||
}
|
||||
|
||||
@State private var firstDisplay: Bool = true
|
||||
|
||||
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
|
||||
}
|
||||
@@ -23,15 +33,20 @@ 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 {
|
||||
BarChartCellShape(value: didCellAppear ? value : 0.0)
|
||||
.fill(gradientColor.linearGradient(from: .bottom, to: .top)) .onAppear {
|
||||
self.didCellAppear = true
|
||||
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
|
||||
}
|
||||
.onDisappear {
|
||||
self.didCellAppear = false
|
||||
self.firstDisplay = true
|
||||
}
|
||||
.transition(.slide)
|
||||
.animation(Animation.spring().delay(self.touchLocation < 0 || !didCellAppear ? Double(self.index) * 0.04 : 0))
|
||||
.animation(Animation.spring().delay(self.touchLocation < 0 || !firstDisplay ? Double(self.index) * 0.04 : 0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,17 +54,17 @@ struct BarChartCell_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
Group {
|
||||
BarChartCell(value: 0, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat())
|
||||
BarChartCell(value: 0, width: 50, numberOfDataPoints: 1, gradientColor: ColorGradient.greenRed, 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())
|
||||
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())
|
||||
}
|
||||
|
||||
Group {
|
||||
BarChartCell(value: 1, gradientColor: ColorGradient.greenRed, touchLocation: CGFloat())
|
||||
BarChartCell(value: 1, gradientColor: ColorGradient.whiteBlack, touchLocation: CGFloat())
|
||||
BarChartCell(value: 1, gradientColor: ColorGradient(.purple), 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())
|
||||
}.environment(\.colorScheme, .dark)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@ 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 {
|
||||
@@ -23,18 +27,20 @@ public struct BarChartRow: View {
|
||||
public var body: some View {
|
||||
GeometryReader { geometry in
|
||||
HStack(alignment: .bottom,
|
||||
spacing: geometry.frame(in: .local).width / CGFloat(chartData.data.count * 3)) {
|
||||
ForEach(0..<chartData.data.count, id: \.self) { index in
|
||||
BarChartCell(value: chartData.normalisedPoints[index],
|
||||
spacing: (geometry.frame(in: .local).width - Constant.spacing) / CGFloat(self.chartData.data.count * 3)) {
|
||||
ForEach(0..<self.chartData.data.count, id: \.self) { index in
|
||||
BarChartCell(value: self.normalizedValue(index: index),
|
||||
index: index,
|
||||
width: Float(geometry.frame(in: .local).width - Constant.spacing),
|
||||
numberOfDataPoints: self.chartData.data.count,
|
||||
gradientColor: self.style.foregroundColor.rotate(for: index),
|
||||
touchLocation: self.touchLocation)
|
||||
.scaleEffect(self.getScaleSize(touchLocation: self.touchLocation, index: index), anchor: .bottom)
|
||||
.animation(Animation.easeIn(duration: 0.2))
|
||||
}
|
||||
// .drawingGroup()
|
||||
// .drawingGroup()
|
||||
}
|
||||
.frame(maxHeight: chartData.isInNegativeDomain ? geometry.size.height / 2 : geometry.size.height)
|
||||
.padding([.top, .leading, .trailing], 10)
|
||||
.gesture(DragGesture()
|
||||
.onChanged({ value in
|
||||
let width = geometry.frame(in: .local).width
|
||||
@@ -52,6 +58,13 @@ public struct BarChartRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Value relative to maximum value
|
||||
/// - Parameter index: index into array of data
|
||||
/// - Returns: data value at given index, divided by data maximum
|
||||
func normalizedValue(index: Int) -> Double {
|
||||
return Double(chartData.points[index])/Double(maxValue)
|
||||
}
|
||||
|
||||
/// Size to scale the touch indicator
|
||||
/// - Parameters:
|
||||
/// - touchLocation: fraction of width where touch is happening
|
||||
@@ -74,11 +87,3 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,57 @@ 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
|
||||
@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))
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
/// 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.
|
||||
@@ -25,36 +62,34 @@ public struct Line: View {
|
||||
public var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
if self.didCellAppear && self.showBackground {
|
||||
LineBackgroundShapeView(chartData: chartData,
|
||||
geometry: geometry,
|
||||
style: style)
|
||||
if self.showFull && self.showBackground {
|
||||
self.getBackgroundPathView()
|
||||
}
|
||||
LineShapeView(chartData: chartData,
|
||||
geometry: geometry,
|
||||
style: style,
|
||||
trimTo: didCellAppear ? 1.0 : 0.0)
|
||||
.animation(.easeIn)
|
||||
self.getLinePathView()
|
||||
if self.showIndicator {
|
||||
IndicatorPoint()
|
||||
.position(self.getClosestPointOnPath(geometry: geometry,
|
||||
touchLocation: self.touchLocation))
|
||||
.position(self.getClosestPointOnPath(touchLocation: self.touchLocation))
|
||||
.rotationEffect(.degrees(180), anchor: .center)
|
||||
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
didCellAppear = true
|
||||
}
|
||||
.onDisappear() {
|
||||
didCellAppear = false
|
||||
self.frame = geometry.frame(in: .local)
|
||||
|
||||
}
|
||||
.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(geometry: geometry, touchLocation: value.location)
|
||||
self.getClosestDataPoint(point: self.getClosestPointOnPath(touchLocation: value.location))
|
||||
self.chartValue.interactionInProgress = true
|
||||
})
|
||||
.onEnded({ value in
|
||||
@@ -70,42 +105,79 @@ 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(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
|
||||
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(geometry: GeometryProxy, touchLocation: CGPoint) {
|
||||
let geometryWidth = geometry.frame(in: .local).width
|
||||
let index = Int(round((touchLocation.x / geometryWidth) * CGFloat(chartData.points.count - 1)))
|
||||
/// 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]
|
||||
}
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// 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: 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))
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -19,5 +19,4 @@ public struct RingsChart: View, ChartBase {
|
||||
RingsChartRow(width:10.0, spacing:5.0, chartData: data, style: style)
|
||||
}
|
||||
|
||||
public init() {}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by Nicolas Savoini on 2020-05-25.
|
||||
//
|
||||
|
||||
@testable import SwiftUICharts
|
||||
import XCTest
|
||||
|
||||
|
||||
Reference in New Issue
Block a user