Reputation: 85
I am trying to use a UIBezierPath
to create a line chart. My goal is to animate this chart and have it appear to be "wavy." But I can't seem to get the path to mirror my data.
Edit: Added Data Points
let dataPoints: [Double]
func wave(at elapsed: Double) -> UIBezierPath {
let amplitude = CGFloat(50) - abs(fmod(CGFloat(elapsed/2), 3) - 1.5) * 100
func f(_ x: Int) -> CGFloat {
return sin(((CGFloat(dataPoints[x]*100) / bounds.width) + CGFloat(elapsed/2)) * 4 * .pi) * amplitude + bounds.midY
}
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: f(0)))
for x in 1..<dataPoints.count {
path.addLine(to: CGPoint(x: CGFloat(x), y: f(x)))
}
return path
}
Upvotes: 1
Views: 421
Reputation: 438287
A few issues:
You defined your f(_:)
, but aren’t using it, but rather using dataPoints
(which I don’t see you populating anywhere). It’s probably easiest to just use your f
function directly.
You are adding cubic Bézier curves to your path, but your control points are the same data points. That will offer no smoothing. The control points really should be points before and after your x
value, along the line tangent to the curve in question (e.g. the along the line defined by the data point and the slope, a.k.a. the first derivative, of your curve).
It wouldn’t be so hard to do this, but the easy solution is to just use addLine
. If you use enough data points, it will appear to be smooth.
When you do this, it won’t be a nice smooth sine curve if using only 12 data points. Instead, use a much larger number (120 or 360 or whatever). For the sake of a single sine curve, don’t worry about the number of data points unless you start to see performance issues.
Your x
values in your CGPoint
values are smaller than you probably intended. If you only go from 0..<12, the resulting path will be only 12 points wide; lol. Use x
values to go all the way across the view in question.
So, you might end up with something like:
func wave(at elapsed: Double) -> UIBezierPath {
let amplitude = CGFloat(50) - abs(fmod(CGFloat(elapsed/2), 3) - 1.5) * 40
func f(_ x: Int) -> CGFloat {
return sin(((CGFloat(x) / bounds.width) + CGFloat(elapsed/2)) * 4 * .pi) * amplitude + bounds.midY
}
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: f(0)))
for x in 1...Int(bounds.width) {
path.addLine(to: CGPoint(x: CGFloat(x), y: f(x)))
}
return path
}
Marry that with a CADisplayLink
to update the curve, and that yields:
Upvotes: 1