如何使用 UIBezierPath 创建带有数据点的曲线图?

Posted

技术标签:

【中文标题】如何使用 UIBezierPath 创建带有数据点的曲线图?【英文标题】:How can I use a UIBezierPath to create a curved line chart with data points? 【发布时间】:2020-03-18 15:55:13 【问题描述】:

我正在尝试使用UIBezierPath 创建折线图。我的目标是为这张图表制作动画,让它看起来像是“波浪状的”。但我似乎无法找到镜像数据的路径。

编辑:添加数据点

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

【问题讨论】:

不清楚。你能画出我们想要的结果吗?你只是想要通过一些给定点的平滑曲线吗?似乎可能与 ***.com/questions/8702696/… 和其他人重复。 顺便说一句,尼克,我注意到你问了很多问题,但没有接受任何答案。如果有人回答问题,您可能会考虑接受答案。见What should I do when someone answers my question? matt,fwiw,那些通用的Hermite and Catmull-Rom splines 在尝试平滑随机数据点(例如与用户手势对应的点)时非常有用,但在这种情况下,函数用于生成数据点是已知且固定的,采用一阶导数来求切线会更高效、更准确。 您已经添加了 dataPoints 变量的声明,但您还没有共享任何填充该数组的代码。而且根本不清楚你为什么需要它,而不是直接使用f(_:),如下所示。我想当你调用f 时,你可以将结果保存在该数组中,但我不明白你为什么需要/想要这样做。您是否出于某种原因需要保存结果? 我刚刚在代码中添加了预期的 dataPoints 用法。这些填充了百分比值(0-100),我正在尝试将路径与这些值的折线图相匹配。 【参考方案1】:

几个问题:

    您定义了f(_:),但没有使用它,而是使用了dataPoints(我看不到您在任何地方填充)。直接使用f 函数可能是最简单的。

    您正在向路径添加三次贝塞尔曲线,但您的控制点是相同的数据点。这不会提供平滑。控制点实际上应该是您的 x 值之前和之后的点,沿着与相关曲线相切的线(例如,沿着由数据点定义的线和曲线的斜率,也就是一阶导数)。

    这样做并不难,但简单的解决方案就是使用addLine。如果你使用足够多的数据点,它会显得很平滑。

    当你这样做时,如果只使用 12 个数据点,它不会是一个很好的平滑正弦曲线。相反,请使用更大的数字(120 或 360 或其他)。为了一条正弦曲线,除非您开始看到性能问题,否则不要担心数据点的数量。

    CGPoint 值中的 x 值可能小于您的预期值。如果您只从 0..x 值一直遍历相关视图。

所以,你可能会得到类似的结果:

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

将其与CADisplayLink 结合以更新曲线,从而产生:

【讨论】:

FWIW,在包括旧 iPhone 6 在内的各种设备上保持完整的 60 fps 没有问题,即使是未经优化的调试版本。

以上是关于如何使用 UIBezierPath 创建带有数据点的曲线图?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 UIBezierPath 从视图中间开始(目前它是从视图的左角开始)

如何使用任何分类器对我的数据进行分类,每个数据点由一组浮点值组成?

如何使用 sklearn 训练算法对数据点进行加权

如何在python中绘制一条线,每个数据点都有一个间隔

C++如何创建异构容器

如何在不丢失阴影饱和度的情况下将带有阴影的 UIBezierpath 转换为 UIImage