iOS经验分享iOS学习征服iOS

如何用Objective-C画颜色渐变的贝塞尔曲线

2017-04-05  本文已影响347人  无忌不悔

在iOS开发中,经常需要用到一些图表来展现数据,为了使图表更加美观直白,需要做一些处理,使得图表在准确展现数据的同时,拥有更好的用户体验。这一点,iOS的系统应用为我们做了很好的示范(如健康app的图表效果)。

健康app官网图

为此,我整理了用OC画颜色渐变的贝塞尔曲线的方法。本文以画光谱曲线为例,用光谱波长值对应颜色值并将多种映射到波长值集中的区域,使图表能展示尽可能多种颜色的渐变效果。

效果图:


渐变曲线

** 话不多说,代码奉上。**

第一步,绘制坐标系

   //主网格曲线视图容器
    UIView *mainContainer = [UIView new];
    _mainContainer = mainContainer;
    [self addSubview:mainContainer];
    //封闭阴影
    CAShapeLayer * backLayer = [CAShapeLayer new];
    _backLayer = backLayer;
    [mainContainer.layer addSublayer:backLayer];
    //网格横线
    CAReplicatorLayer *rowReplicatorLayer = [CAReplicatorLayer new];
    _rowReplicatorLayer = rowReplicatorLayer;
    rowReplicatorLayer.position = CGPointMake(0, 0);
    CALayer *rowBackLine = [CALayer new];
    _rowBackLine = rowBackLine;
    [rowReplicatorLayer addSublayer:rowBackLine];
    [mainContainer.layer addSublayer:rowReplicatorLayer];
    //网格列线
    CAReplicatorLayer *columnReplicatorLayer = [CAReplicatorLayer new];
    _columnReplicatorLayer = columnReplicatorLayer;
    columnReplicatorLayer.position = CGPointMake(0, 0);
    CALayer *columnBackLine = [CALayer new];
    _columnBackLine = columnBackLine;
    [columnReplicatorLayer addSublayer:columnBackLine];
    [mainContainer.layer addSublayer:columnReplicatorLayer];
    //行信息labels容器
    UIView *rowLabelsContainer = [UIView new];
    _rowLabelsContainer = rowLabelsContainer;
    [self addSubview:rowLabelsContainer];
    //列信息labels容器
    UIView *columnLabelsContainer = [UIView new];
    _columnLabelsContainer = columnLabelsContainer;
    [self addSubview:columnLabelsContainer];

第二步,画贝塞尔曲线

对传入的数据数组进行for循环,在循环中用UIBezierPath创建path对象,设置其起点坐标和终点坐标,画线。

 NSMutableArray *temp = [NSMutableArray arrayWithArray:_pointValues];
    for (NSInteger i = 0; i < _pointArray.count - 1; i ++) {
        UIBezierPath * path = [UIBezierPath bezierPath];
        NSValue *startPointValue = _pointArray[i];
        NSValue *endPointValue = _pointArray[i + 1];
        CGPoint startPoint = [startPointValue CGPointValue];
        CGPoint endPoint   = [endPointValue CGPointValue];
        [path moveToPoint:startPoint];
        if (startPointValue == _pointArray[0]) {
            continue;
        }
        [path addLineToPoint:endPoint];
        //主曲线
        path = [path smoothedPathWithGranularity:10];
        CAShapeLayer *curveLineLayer = [CAShapeLayer new];
        curveLineLayer.backgroundColor = [UIColor whiteColor].CGColor;
        curveLineLayer.fillColor = nil;
        curveLineLayer.lineJoin = kCALineJoinRound;
        curveLineLayer.lineCap = kCALineCapRound;
        curveLineLayer.lineWidth = 1.5;
        curveLineLayer.path = path.CGPath;
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSDictionary *dict = temp[i];
            int yValue = [[dict objectForKey:XWCurveViewPointValuesColumnValueKey] intValue];
            UIColor *strokeColor = [self ordinateValue2RGB:yValue];
            curveLineLayer.strokeColor = strokeColor.CGColor;
            [self.mainContainer.layer addSublayer:curveLineLayer];
            curveLineLayer.strokeEnd = 1;
            if (_drawWithAnimation) {
                CABasicAnimation *pointAnim = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
                pointAnim.fromValue = @0;
                pointAnim.toValue = @1;
                pointAnim.duration = _drawAnimationDuration;
                [_curveLineLayer addAnimation:pointAnim forKey:@"drawLine"];
            }
        });
    }

其中,ordinateValue2RGB方法就是根据纵坐标的波长值对应RGB的算法。该算法是根据波长颜色对应算法针对可视范围的改写。首先将传入值映射关系进行重新对应。

- (UIColor *)ordinateValue2RGB:(int)value {
    int newValue = 0;
    int offset = 1;
    if ((value >= 0) && (value < 512)) {
        newValue = (int)((double) value / (double)8.53);
        newValue = newValue+340;
    }else if ((value >= 512) && (value < 2048)) {
        offset = value - 512;
        newValue = (int)((double) offset / (double)7.68);
        newValue = newValue + 400;
    }else if (value >= 2048) {
        offset = value - 2048;
        newValue = (int)((double) offset / (double)8.53);
        newValue = newValue + 600;
    }
    return [self wavelength2RGB:newValue];
}

再根据获取的对应波长计算RGB值。

- (UIColor *)wavelength2RGB:(int)wavelength {
    double Gamma = 0.50;
    int IntensityMax = 255;
    double red, green, blue;
    if((wavelength >= 340) && (wavelength < 440)){
        red = ((double) -(wavelength - 440)) / ((double) (440 - 340));
        green = 0.0;
        blue = 1.0;
    }else if((wavelength >= 440) && (wavelength < 490)){
        red = 0.0;
        green = ((double)(wavelength - 440)) /((double) (490 - 440));
        blue = 1.0;
    }else if((wavelength >= 490) && (wavelength < 510)){
        red = 0.0;
        green = 1.0;
        blue = ((double)-(wavelength - 510) )/ ((double)(510 - 490));
    }else if((wavelength >= 510) && (wavelength < 580)){
        red = ((double)(wavelength - 510)) / ((double) (580 - 510));
        green = 1.0;
        blue = 0.0;
    }else if((wavelength >= 580) && (wavelength < 645)){
        red = 1.0;
        green = ((double) - (wavelength - 645) ) / ((double) (645 - 580));
        blue = 0.0;
    }else if((wavelength >= 645) && (wavelength < 781)){
        red = 1.0;
        green = 0.0;
        blue = 0.0;
    }else{
        red = 0.0;
        green = 0.0;
        blue = 0.0;
    }
    double factor;
    // Let the intensity fall off near the vision limits
    if((wavelength >= 380) && (wavelength < 420)){
        factor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380);
    }else if((wavelength >= 420) && (wavelength < 701)){
        factor = 1.0;
    }else if((wavelength >= 701) && (wavelength < 781)){
        factor = 0.3 + 0.7 * (780 - wavelength) / (780 - 700);
    }else{
        factor = 0.0;
    }
    if (red != 0){
        red = round(IntensityMax * pow(red * factor, Gamma));
    }
    if (green != 0){
        green = round(IntensityMax * pow(green * factor, Gamma));
    }
    if (blue != 0){
        blue = round(IntensityMax * pow(blue * factor, Gamma));
    }

    return [UIColor colorWithRed:(CGFloat)red/255.0 green:(CGFloat)green/255.0 blue:(CGFloat)blue/255.0 alpha:1.0];
}

最后,使用smoothedPathWithGranularity方法对曲线进行平滑处理。

- (UIBezierPath*)smoothedPathWithGranularity:(NSInteger)granularity;
{
    NSMutableArray *points = [pointsFromBezierPath(self) mutableCopy];
    
    if (points.count < 4) return [self copy];
    
    // Add control points to make the math make sense
    [points insertObject:[points objectAtIndex:0] atIndex:0];
    [points addObject:[points lastObject]];
    
    UIBezierPath *smoothedPath = [self copy];
    [smoothedPath removeAllPoints];
    
    [smoothedPath moveToPoint:POINT(0)];
    
    for (NSUInteger index = 1; index < points.count - 2; index++)
    {
        CGPoint p0 = POINT(index - 1);
        CGPoint p1 = POINT(index);
        CGPoint p2 = POINT(index + 1);
        CGPoint p3 = POINT(index + 2);
        
        // now add n points starting at p1 + dx/dy up until p2 using Catmull-Rom splines
        for (int i = 1; i < granularity; i++)
        {
            float t = (float) i * (1.0f / (float) granularity);
            float tt = t * t;
            float ttt = tt * t;
            
            // 中间点            
            CGPoint pi;
            pi.x = 0.5 * (2 * p1.x+ (p2.x- p0.x) * t + (2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
            pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t + (2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
            [smoothedPath addLineToPoint:pi];
        }
        
        // Now add p2
        [smoothedPath addLineToPoint:p2];
    }
    
    // finish by adding the last point
    [smoothedPath addLineToPoint:POINT(points.count - 1)];
    
    return smoothedPath;
}

再对颜色和背景进行适当美化,就能够画出效果不错的颜色渐变曲线了。

上一篇下一篇

猜你喜欢

热点阅读