-
Notifications
You must be signed in to change notification settings - Fork 653
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix line chart view indicator point (#40)
- Loading branch information
Showing
2 changed files
with
252 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
|