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

9f14a46bf895

健康app官网图

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

效果图:

9f14a46bf895

渐变曲线

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

第一步,绘制坐标系

//主网格曲线视图容器

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;

}

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

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐