简体   繁体   English

如何在iOS中绘制平滑曲线的折线图?

[英]How to draw line chart with smooth curve in iOS?

I have some points like (x1,y1), (x2,y2), (x3,y3)...我有一些像 (x1,y1), (x2,y2), (x3,y3)...

Now I want to draw a chart with smooth curve?现在我想绘制一个曲线平滑的图表?

I'm trying to draw as below我正在尝试绘制如下

-(void)drawPrices
{
    NSInteger count = self.prices.count;
    
    UIBezierPath *path = [UIBezierPath bezierPath]; 
    path.lineCapStyle = kCGLineCapRound;

    for(int i=0; i<count-1; i++)
    {
        CGPoint controlPoint[2];
        
        CGPoint p = [self pointWithIndex:i inData:self.prices];
        if(i==0)
        {
            [path moveToPoint:p];
        }

        CGPoint nextPoint, previousPoint, m;
        nextPoint = [self pointWithIndex:i+1 inData:self.prices];
        previousPoint = [self pointWithIndex:i-1 inData:self.prices];
        
        if(i > 0) {
            m.x = (nextPoint.x - previousPoint.x) / 2;
            m.y = (nextPoint.y - previousPoint.y) / 2;
        } else {
            m.x = (nextPoint.x - p.x) / 2;
            m.y = (nextPoint.y - p.y) / 2;
        }
        
        controlPoint[0].x = p.x + m.x * 0.2;
        controlPoint[0].y = p.y + m.y * 0.2;
        
        // Second control point
        nextPoint = [self pointWithIndex:i+2 inData:self.prices];
        previousPoint = [self pointWithIndex:i inData:self.prices];
        p = [self pointWithIndex:i + 1 inData:self.prices];
        m = zeroPoint;
        
        if(i < self.prices.count - 2) {
            m.x = (nextPoint.x - previousPoint.x) / 2;
            m.y = (nextPoint.y - previousPoint.y) / 2;
        } else {
            m.x = (p.x - previousPoint.x) / 2;
            m.y = (p.y - previousPoint.y) / 2;
        }
        
        controlPoint[1].x = p.x - m.x * 0.2;
        controlPoint[1].y = p.y - m.y * 0.2;
        
        [path addCurveToPoint:p controlPoint1:controlPoint[0] controlPoint2:controlPoint[1]];
    }
    
    CAShapeLayer *lineLayer = [CAShapeLayer layer];
    lineLayer.path = path.CGPath;
    lineLayer.lineWidth = LINE_WIDTH;
    lineLayer.strokeColor = _priceColor.CGColor;
    lineLayer.fillColor = [UIColor clearColor].CGColor;

    [self.layer addSublayer:lineLayer];
}

but in some situation, the line will "go back" like但在某些情况下,该线路会“返回”,例如

Is there any better way to do that?有没有更好的方法来做到这一点?

I've got you another solution which I used to success.我为您提供了另一个我曾经成功的解决方案。 It requires SpriteKit, which has a fantastic tool called SKKeyFrameSequence which is capable of spline interpolation as shown in this tutorial provided by Apple.它需要 SpriteKit,它有一个很棒的工具,称为SKKeyFrameSequence ,它能够进行样条插值,如 Apple 提供的本教程所示。

So the idea is that you'll create the correct SKKeyFrameSequence object like this (assuming your data is in an array of (CGFloat, CGFloat) (x, y) tuples):所以我们的想法是,您将像这样创建正确的 SKKeyFrameSequence 对象(假设您的数据位于(CGFloat, CGFloat) (x, y) 元组的数组中):

let xValues = data.map { $0.0 }
let yValues = data.map { $0.1 }
let sequence = SKKeyFrameSequence(keyFrameValues: yValues,
                                  times: xValues.map { NSNumber($0) })
sequence.interpolationMode = .spline

let xMin = xValues.min()!
let xMax = xValues.max()!

Then, if you divide the interpolated spline into 200 pieces (change this value if you want, but for me this resulted in smooth waves to human eyes), you can draw a path consisting of small lines.然后,如果您将插值样条分成 200 条(如果您愿意,可以更改此值,但对我而言,这会导致人眼看到平滑的波浪),您可以绘制一条由小线组成的路径。

var splinedValues = [(CGFloat, CGFloat)]()
stride(from: xMin, to: xMax, by: (xMax - xMin) / 200).forEach {
    splinedValues.append((CGFloat($0),
                          sequence.sample(atTime: CGFloat($0)) as! CGFloat))
}

Then finally the path (I will use SwiftUI, but you can use UIKit the same way too.):最后是路径(我将使用 SwiftUI,但您也可以以相同的方式使用 UIKit。):

Path { path in
    path.move(to: CGPoint(x: splinedValues[0].0, y: splinedValues[0].1))

    for splineValue in splinedValues.dropFirst() {
        path.addLine(to: CGPoint(x: splineValue.0, y: splineValue.1))
    }
}

For the values对于值

[(1, 4), (2, 6), (3, 7), (4, 5), (5, 3), (6, -1), (7, -2), (8, -2.5), (9, -2), (10, 0), (11, 4)]

I've gotten a graph like this with the method described above: (I added the points too to evaluate the result better)我用上面描述的方法得到了这样的图:(我也添加了点以更好地评估结果)

在此处输入图片说明

I find the answer at Draw Graph curves with UIBezierPath我在使用 UIBezierPath 绘制图形曲线找到了答案

And try implement with the code并尝试使用代码实现

+ (UIBezierPath *)quadCurvedPathWithPoints:(NSArray *)points
{
    UIBezierPath *path = [UIBezierPath bezierPath];

    NSValue *value = points[0];
    CGPoint p1 = [value CGPointValue];
    [path moveToPoint:p1];

    if (points.count == 2) {
        value = points[1];
        CGPoint p2 = [value CGPointValue];
        [path addLineToPoint:p2];
        return path;
    }

    for (NSUInteger i = 1; i < points.count; i++) {
        value = points[i];
        CGPoint p2 = [value CGPointValue];

        CGPoint midPoint = midPointForPoints(p1, p2);
        [path addQuadCurveToPoint:midPoint controlPoint:controlPointForPoints(midPoint, p1)];
        [path addQuadCurveToPoint:p2 controlPoint:controlPointForPoints(midPoint, p2)];

        p1 = p2;
    }
    return path;
}

static CGPoint midPointForPoints(CGPoint p1, CGPoint p2) {
    return CGPointMake((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
}

static CGPoint controlPointForPoints(CGPoint p1, CGPoint p2) {
    CGPoint controlPoint = midPointForPoints(p1, p2);
    CGFloat diffY = abs(p2.y - controlPoint.y);

    if (p1.y < p2.y)
        controlPoint.y += diffY;
    else if (p1.y > p2.y)
        controlPoint.y -= diffY;

    return controlPoint;
}

Thanks user1244109 ^_^.感谢用户 1244109 ^_^。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM