在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;
}
再对颜色和背景进行适当美化,就能够画出效果不错的颜色渐变曲线了。