Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88db9aeafe | |||
| 37c51d9b46 | |||
| 75df39fc1f | |||
| 6b5affa46e |
@@ -153,7 +153,7 @@ BarChartView(data: [8,23,54,32,12,37,7,23,43], title: "Title", form: ChartForm.s
|
||||
## Pie charts
|
||||

|
||||
|
||||
You can add a line chart with the following code:
|
||||
You can add a pie chart with the following code:
|
||||
|
||||
```swift
|
||||
PieChartView(data: [8,23,54,32], title: "Title", legend: "Legendary") // legend is optional
|
||||
|
||||
@@ -129,8 +129,9 @@ public struct ChartStyle {
|
||||
}
|
||||
|
||||
public class ChartData: ObservableObject {
|
||||
@Published var points: [(String,Double)] = [(String,Double)]()
|
||||
@Published var points: [(String,Double)]
|
||||
var valuesGiven: Bool = false
|
||||
|
||||
public init<N: BinaryFloatingPoint>(points:[N]) {
|
||||
self.points = points.map{("", Double($0))}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,13 @@ struct Line: View {
|
||||
.rotationEffect(.degrees(180), anchor: .center)
|
||||
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
|
||||
.animation(.easeOut(duration: 1.2))
|
||||
.onAppear(){
|
||||
self.showFull.toggle()
|
||||
}.drawingGroup()
|
||||
.onAppear {
|
||||
self.showFull = true
|
||||
}
|
||||
.onDisappear {
|
||||
self.showFull = false
|
||||
}
|
||||
.drawingGroup()
|
||||
if(self.showIndicator) {
|
||||
IndicatorPoint()
|
||||
.position(self.getClosestPointOnPath(touchLocation: self.touchLocation))
|
||||
@@ -71,8 +75,7 @@ struct Line: View {
|
||||
}
|
||||
|
||||
func getClosestPointOnPath(touchLocation: CGPoint) -> CGPoint {
|
||||
let percentage:CGFloat = min(max(touchLocation.x,0)/self.frame.width,1)
|
||||
let closest = self.path.percentPoint(percentage)
|
||||
let closest = self.path.point(to: touchLocation.x)
|
||||
return closest
|
||||
}
|
||||
|
||||
@@ -147,22 +150,6 @@ extension Path {
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
|
||||
func percentPoint(_ percent: CGFloat) -> CGPoint {
|
||||
// percent difference between points
|
||||
let diff: CGFloat = 0.001
|
||||
let comp: CGFloat = 1 - diff
|
||||
|
||||
// handle limits
|
||||
let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent)
|
||||
|
||||
let f = pct > comp ? comp : pct
|
||||
let t = pct > comp ? 1 : pct + diff
|
||||
let tp = self.trimmedPath(from: f, to: t)
|
||||
|
||||
return CGPoint(x: tp.boundingRect.midX, y: tp.boundingRect.midY)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Line_Previews: PreviewProvider {
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by xspyhack on 2020/1/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension Path {
|
||||
func trimmedPath(for percent: CGFloat) -> Path {
|
||||
// percent difference between points
|
||||
let boundsDistance: CGFloat = 0.001
|
||||
let completion: CGFloat = 1 - boundsDistance
|
||||
|
||||
let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent)
|
||||
|
||||
let start = pct > completion ? completion : pct - boundsDistance
|
||||
let end = pct > completion ? 1 : pct + boundsDistance
|
||||
return trimmedPath(from: start, to: end)
|
||||
}
|
||||
|
||||
func point(for percent: CGFloat) -> CGPoint {
|
||||
let path = trimmedPath(for: percent)
|
||||
return CGPoint(x: path.boundingRect.midX, y: path.boundingRect.midY)
|
||||
}
|
||||
|
||||
func point(to maxX: CGFloat) -> CGPoint {
|
||||
let total = length
|
||||
let sub = length(to: maxX)
|
||||
let percent = sub / total
|
||||
return point(for: percent)
|
||||
}
|
||||
|
||||
var length: CGFloat {
|
||||
var ret: CGFloat = 0.0
|
||||
var start: CGPoint?
|
||||
var point = CGPoint.zero
|
||||
|
||||
forEach { ele in
|
||||
switch ele {
|
||||
case .move(let to):
|
||||
if start == nil {
|
||||
start = to
|
||||
}
|
||||
point = to
|
||||
case .line(let to):
|
||||
ret += point.line(to: to)
|
||||
point = to
|
||||
case .quadCurve(let to, let control):
|
||||
ret += point.quadCurve(to: to, control: control)
|
||||
point = to
|
||||
case .curve(let to, let control1, let control2):
|
||||
ret += point.curve(to: to, control1: control1, control2: control2)
|
||||
point = to
|
||||
case .closeSubpath:
|
||||
if let to = start {
|
||||
ret += point.line(to: to)
|
||||
point = to
|
||||
}
|
||||
start = nil
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func length(to maxX: CGFloat) -> CGFloat {
|
||||
var ret: CGFloat = 0.0
|
||||
var start: CGPoint?
|
||||
var point = CGPoint.zero
|
||||
var finished = false
|
||||
|
||||
forEach { ele in
|
||||
if finished {
|
||||
return
|
||||
}
|
||||
switch ele {
|
||||
case .move(let to):
|
||||
if to.x > maxX {
|
||||
finished = true
|
||||
return
|
||||
}
|
||||
if start == nil {
|
||||
start = to
|
||||
}
|
||||
point = to
|
||||
case .line(let to):
|
||||
if to.x > maxX {
|
||||
finished = true
|
||||
ret += point.line(to: to, x: maxX)
|
||||
return
|
||||
}
|
||||
ret += point.line(to: to)
|
||||
point = to
|
||||
case .quadCurve(let to, let control):
|
||||
if to.x > maxX {
|
||||
finished = true
|
||||
ret += point.quadCurve(to: to, control: control, x: maxX)
|
||||
return
|
||||
}
|
||||
ret += point.quadCurve(to: to, control: control)
|
||||
point = to
|
||||
case .curve(let to, let control1, let control2):
|
||||
if to.x > maxX {
|
||||
finished = true
|
||||
ret += point.curve(to: to, control1: control1, control2: control2, x: maxX)
|
||||
return
|
||||
}
|
||||
ret += point.curve(to: to, control1: control1, control2: control2)
|
||||
point = to
|
||||
case .closeSubpath:
|
||||
fatalError("Can't include closeSubpath")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
extension CGPoint {
|
||||
func point(to: CGPoint, x: CGFloat) -> CGPoint {
|
||||
let a = (to.y - self.y) / (to.x - self.x)
|
||||
let y = self.y + (x - self.x) * a
|
||||
return CGPoint(x: x, y: y)
|
||||
}
|
||||
|
||||
func line(to: CGPoint) -> CGFloat {
|
||||
dist(to: to)
|
||||
}
|
||||
|
||||
func line(to: CGPoint, x: CGFloat) -> CGFloat {
|
||||
dist(to: point(to: to, x: x))
|
||||
}
|
||||
|
||||
func quadCurve(to: CGPoint, control: CGPoint) -> CGFloat {
|
||||
var dist: CGFloat = 0
|
||||
let steps: CGFloat = 100
|
||||
|
||||
for i in 0..<Int(steps) {
|
||||
let t0 = CGFloat(i) / steps
|
||||
let t1 = CGFloat(i+1) / steps
|
||||
let a = point(to: to, t: t0, control: control)
|
||||
let b = point(to: to, t: t1, control: control)
|
||||
|
||||
dist += a.line(to: b)
|
||||
}
|
||||
return dist
|
||||
}
|
||||
|
||||
func quadCurve(to: CGPoint, control: CGPoint, x: CGFloat) -> CGFloat {
|
||||
var dist: CGFloat = 0
|
||||
let steps: CGFloat = 100
|
||||
|
||||
for i in 0..<Int(steps) {
|
||||
let t0 = CGFloat(i) / steps
|
||||
let t1 = CGFloat(i+1) / steps
|
||||
let a = point(to: to, t: t0, control: control)
|
||||
let b = point(to: to, t: t1, control: control)
|
||||
|
||||
if a.x >= x {
|
||||
return dist
|
||||
} else if b.x > x {
|
||||
dist += a.line(to: b, x: x)
|
||||
return dist
|
||||
} else if b.x == x {
|
||||
dist += a.line(to: b)
|
||||
return dist
|
||||
}
|
||||
|
||||
dist += a.line(to: b)
|
||||
}
|
||||
return dist
|
||||
}
|
||||
|
||||
func point(to: CGPoint, t: CGFloat, control: CGPoint) -> CGPoint {
|
||||
let x = CGPoint.value(x: self.x, y: to.x, t: t, c: control.x)
|
||||
let y = CGPoint.value(x: self.y, y: to.y, t: t, c: control.y)
|
||||
|
||||
return CGPoint(x: x, y: y)
|
||||
}
|
||||
|
||||
func curve(to: CGPoint, control1: CGPoint, control2: CGPoint) -> CGFloat {
|
||||
var dist: CGFloat = 0
|
||||
let steps: CGFloat = 100
|
||||
|
||||
for i in 0..<Int(steps) {
|
||||
let t0 = CGFloat(i) / steps
|
||||
let t1 = CGFloat(i+1) / steps
|
||||
|
||||
let a = point(to: to, t: t0, control1: control1, control2: control2)
|
||||
let b = point(to: to, t: t1, control1: control1, control2: control2)
|
||||
|
||||
dist += a.line(to: b)
|
||||
}
|
||||
|
||||
return dist
|
||||
}
|
||||
|
||||
func curve(to: CGPoint, control1: CGPoint, control2: CGPoint, x: CGFloat) -> CGFloat {
|
||||
var dist: CGFloat = 0
|
||||
let steps: CGFloat = 100
|
||||
|
||||
for i in 0..<Int(steps) {
|
||||
let t0 = CGFloat(i) / steps
|
||||
let t1 = CGFloat(i+1) / steps
|
||||
|
||||
let a = point(to: to, t: t0, control1: control1, control2: control2)
|
||||
let b = point(to: to, t: t1, control1: control1, control2: control2)
|
||||
|
||||
if a.x >= x {
|
||||
return dist
|
||||
} else if b.x > x {
|
||||
dist += a.line(to: b, x: x)
|
||||
return dist
|
||||
} else if b.x == x {
|
||||
dist += a.line(to: b)
|
||||
return dist
|
||||
}
|
||||
|
||||
dist += a.line(to: b)
|
||||
}
|
||||
|
||||
return dist
|
||||
}
|
||||
|
||||
func point(to: CGPoint, t: CGFloat, control1: CGPoint, control2: CGPoint) -> CGPoint {
|
||||
let x = CGPoint.value(x: self.x, y: to.x, t: t, c1: control1.x, c2: control2.x)
|
||||
let y = CGPoint.value(x: self.y, y: to.y, t: t, c1: control1.y, c2: control2.x)
|
||||
|
||||
return CGPoint(x: x, y: y)
|
||||
}
|
||||
|
||||
static func value(x: CGFloat, y: CGFloat, t: CGFloat, c: CGFloat) -> CGFloat {
|
||||
var value: CGFloat = 0.0
|
||||
// (1-t)^2 * p0 + 2 * (1-t) * t * c1 + t^2 * p1
|
||||
value += pow(1-t, 2) * x
|
||||
value += 2 * (1-t) * t * c
|
||||
value += pow(t, 2) * y
|
||||
return value
|
||||
}
|
||||
|
||||
static func value(x: CGFloat, y: CGFloat, t: CGFloat, c1: CGFloat, c2: CGFloat) -> CGFloat {
|
||||
var value: CGFloat = 0.0
|
||||
// (1-t)^3 * p0 + 3 * (1-t)^2 * t * c1 + 3 * (1-t) * t^2 * c2 + t^3 * p1
|
||||
value += pow(1-t, 3) * x
|
||||
value += 3 * pow(1-t, 2) * t * c1
|
||||
value += 3 * (1-t) * pow(t, 2) * c2
|
||||
value += pow(t, 3) * y
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user