1

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
}
Nick
  • 75
  • 1
  • 8
  • Unclear. Could you draw us the desired outcome? Do you just want a smooth curve thru some given points? Seems likely duplicate of https://stackoverflow.com/questions/8702696/drawing-smooth-curves-methods-needed/8731493?r=SearchResults#8731493 and others. – matt Mar 18 '20 at 16:04
  • matt, fwiw, those generic [Hermite and Catmull-Rom splines](https://stackoverflow.com/a/34583708/1271826) are great when trying to smooth random data points (e.g. points corresponding to user gestures), but in a case like this where the function used to generate for the data points is known and fixed, taking the first derivative to find the tangent is going to be both more efficient and more accurate. – Rob Mar 18 '20 at 18:52
  • You’ve added your declaration of your `dataPoints` variable, but you haven’t shared any code that populates that array. And it’s not clear why you need it at all, rather than just using `f(_:)` directly, like shown below. I guess that when you call `f` you could save the result in that array, but I’m not seeing why you’d need/want to do that. Do you need to save the results for some reason? – Rob Mar 18 '20 at 20:45
  • I've just added my expected usage of dataPoints in the code. These are populated with percentage values (0-100) and I am trying to match the path to a line chart of these values. – Nick Mar 18 '20 at 22:33

1 Answers1

1

A few issues:

  1. 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.

  2. 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.

  3. 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.

  4. 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:

sine curve

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • FWIW, this had no problem maintaining a full 60 fps on variety of devices, including an old iPhone 6, even with an unoptimized debug build. – Rob Mar 18 '20 at 18:29