Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 1 addition & 18 deletions Sources/SwiftUICharts/LineChart/Line.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,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
}

Expand Down Expand Up @@ -151,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 {
Expand Down
251 changes: 251 additions & 0 deletions Sources/SwiftUICharts/LineChart/Path+QuadCurve.swift
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
}
}