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

时间:2016-04-06 13:49:49

标签: ios charts curve

我有一些观点 (x1,y1),(x2,y2),(x3,y3)......

现在我想绘制一条曲线平滑的图表?

我正在尝试绘制如下

-(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];
}

但在某些情况下,这条线会像“回去”一样

有没有更好的方法呢?

2 个答案:

答案 0 :(得分:5)

我为您提供了另一个我曾经成功的解决方案。 它需要SpriteKit,它具有一个名为SKKeyFrameSequence的出色工具,该工具能够进行样条插值,如Apple提供的this tutorial所示。

因此,我们的想法是,您将像这样创建正确的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()!

然后,如果将插补的样条曲线分成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))
}

然后是路径(我将使用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))
    }
}

对于值

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

我已经用上述方法得到了这样的图形:(我也添加了点以更好地评估结果)

enter image description here

答案 1 :(得分:0)

我在Draw Graph curves with UIBezierPath

找到答案

尝试使用代码实现

+ (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;
}

感谢user1244109 ^ _ ^。