mastodon-ios/WidgetExtension/LightChart/Charts/LineChart.swift

139 lines
4.9 KiB
Swift

//
// LineChart.swift
//
//
// Created by Alexey Pichukov on 19.08.2020.
//
import SwiftUI
public struct LineChart: View {
private let data: [Double]
private let frame: CGRect
private let offset: Double
private let type: ChartVisualType
private let currentValueLineType: CurrentValueLineType
private var points: [CGPoint] = []
/// Creates a new `LineChart`
///
/// - Parameters:
/// - data: A data set that should be presented on the chart
/// - frame: A frame from the parent view
/// - visualType: A type of chart, `.outline` by default
/// - offset: An offset for the chart, a space below the chart in percentage (0 - 1)
/// For example `offset: 0.2` means that the chart will occupy 80% of the upper
/// part of the view
/// - currentValueLineType: A type of current value line (`none` for no line on chart)
public init(data: [Double],
frame: CGRect,
visualType: ChartVisualType = .outline(color: .red, lineWidth: 2),
offset: Double = 0,
currentValueLineType: CurrentValueLineType = .none) {
self.data = data
self.frame = frame
self.type = visualType
self.offset = offset
self.currentValueLineType = currentValueLineType
self.points = points(forData: data,
frame: frame,
offset: offset,
lineWidth: lineWidth(visualType: visualType))
}
public var body: some View {
ZStack {
chart
.rotationEffect(.degrees(180), anchor: .center)
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
.drawingGroup()
line
.rotationEffect(.degrees(180), anchor: .center)
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
.drawingGroup()
}
}
private var chart: some View {
switch type {
case .outline(let color, let lineWidth):
return AnyView(linePath(points: points)
.stroke(color, style: StrokeStyle(lineWidth: lineWidth, lineJoin: .round)))
case .filled(let color, let lineWidth):
return AnyView(ZStack {
linePathGradient(points: points)
.fill(LinearGradient(
gradient: .init(colors: [color.opacity(0.2), color.opacity(0.02)]),
startPoint: .init(x: 0.5, y: 1),
endPoint: .init(x: 0.5, y: 0)
))
linePath(points: points)
.stroke(color, style: StrokeStyle(lineWidth: lineWidth, lineJoin: .round))
})
case .customFilled(let color, let lineWidth, let fillGradient):
return AnyView(ZStack {
linePathGradient(points: points)
.fill(fillGradient)
linePath(points: points)
.stroke(color, style: StrokeStyle(lineWidth: lineWidth, lineJoin: .round))
})
}
}
private var line: some View {
switch currentValueLineType {
case .none:
return AnyView(EmptyView())
case .line(let color, let lineWidth):
return AnyView(
currentValueLinePath(points: points)
.stroke(color, style: StrokeStyle(lineWidth: lineWidth))
)
case .dash(let color, let lineWidth, let dash):
return AnyView(
currentValueLinePath(points: points)
.stroke(color, style: StrokeStyle(lineWidth: lineWidth, dash: dash))
)
}
}
// MARK: private functions
private func linePath(points: [CGPoint]) -> Path {
var path = Path()
guard points.count > 1 else {
return path
}
path.move(to: points[0])
for i in 1..<points.count {
path.addLine(to: points[i])
}
return path
}
private func linePathGradient(points: [CGPoint]) -> Path {
var path = linePath(points: points)
guard let lastPoint = points.last else {
return path
}
path.addLine(to: CGPoint(x: lastPoint.x, y: 0))
path.addLine(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: points[0].y))
return path
}
private func currentValueLinePath(points: [CGPoint]) -> Path {
var path = Path()
guard let lastPoint = points.last else {
return path
}
path.move(to: CGPoint(x: 0, y: lastPoint.y))
path.addLine(to: lastPoint)
return path
}
}
extension LineChart: DataRepresentable { }