雷达图多用在游戏人物属性分布、成绩分布、个人画像等诸多场景。传统的雷达图分为单个对象和多个对象,前者由一组连续的点构成一个面,分别设置这个面的内部填充颜色和轮廓渲染颜色即可,后者则是多个面的叠加,原理相同。
由于工作需要,需求方设计了一个分块的雷达图,在GitHub上发现一个不错的基础版本ZFChart(在此感谢此道友),修改了并添加了一些属性和方法,制作了如下的实现形式:
ZFChat中的雷达图
首先,看一下ZFChat中的使用方法:
- (void)viewDidLoad{
[super viewDidLoad];
self.radarChart = [[ZFRadarChart alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - NAVIGATIONBAR_HEIGHT)];
self.radarChart.dataSource = self;
self.radarChart.delegate = self;
self.radarChart.unit = @" €";
self.radarChart.itemFont = [UIFont systemFontOfSize:12.f];
self.radarChart.valueFont = [UIFont systemFontOfSize:12.f];
self.radarChart.polygonLineWidth = 2.f;
self.radarChart.valueType = kValueTypeDecimal;
self.radarChart.valueTextColor = ZFOrange;
[self.view addSubview:self.radarChart];
[self.radarChart strokePath];
}
#pragma mark - ZFRadarChartDataSource
- (NSArray *)itemArrayInRadarChart:(ZFRadarChart *)radarChart{
return @[@"item 1", @"item 2", @"item 3", @"item 4", @"item 5"];
}
- (NSArray *)valueArrayInRadarChart:(ZFRadarChart *)radarChart{
return @[@"4", @"10", @"4", @"9", @"7"];
}
- (CGFloat)maxValueInRadarChart:(ZFRadarChart *)radarChart{
return 10.f;
}
#pragma mark - ZFRadarChartDelegate
- (CGFloat)radiusForRadarChart:(ZFRadarChart *)radarChart{
return (SCREEN_WIDTH - 100) / 2;
}
这段代码描述了单个对象雷达图的创建、数据源和代理函数的实现。彩色雷达图也基于同样的实现方式,具体上则是,修改了其内部的
三个子类:
图片.png
这里先说下思路:
- 1.获取各个顶点的坐标pointArray
- 2.将pointArray扩展成2倍顶点个数的extendArray
- 3.每次从extendArray中取出2个连续的点,与雷达图圆心共同构成一个三角形
- 4.使用预先设置好的颜色数组,填充每次构成的三角形
- 5.设置一个从初始到完成的动画效果
说明:2中的扩展方法:依次取相邻pointArray中的两个点,取其中二维空间的中间点。
代码如下:
- (void)getDescribePoint{
[self.describePointArray removeAllObjects];
_startAngle = -90.f;
//获取第一个item半径
_currentRadius = [_radiusArray.firstObject floatValue];
// UIBezierPath * bezier = [UIBezierPath bezierPath];
// [bezier moveToPoint:CGPointMake(_polygonCenter.x, _polygonCenter.y - _currentRadius)];
[self.describePointArray addObject:[NSValue valueWithCGPoint:CGPointMake(_polygonCenter.x, _polygonCenter.y - _currentRadius)]];
for (NSInteger i = 1; i < _radiusArray.count; i++) {
_currentRadarAngle = _averageRadarAngle * i;
//计算每个item的角度
_endAngle = _startAngle + _averageRadarAngle;
//获取当前item半径
_currentRadius = [_radiusArray[i] floatValue];
if (_endAngle > -90.f && _endAngle <= 0.f) {
_endXPos = _polygonCenter.x + fabs(-(_currentRadius * ZFSin(_currentRadarAngle)));
_endYPos = _polygonCenter.y - fabs(_currentRadius * ZFCos(_currentRadarAngle));
}else if (_endAngle > 0.f && _endAngle <= 90.f){
_endXPos = _polygonCenter.x + fabs(-(_currentRadius * ZFSin(_currentRadarAngle)));
_endYPos = _polygonCenter.y + fabs(_currentRadius * ZFCos(_currentRadarAngle));
}else if (_endAngle > 90.f && _endAngle <= 180.f){
_endXPos = _polygonCenter.x - fabs(-(_currentRadius * ZFSin(_currentRadarAngle)));
_endYPos = _polygonCenter.y + fabs(_currentRadius * ZFCos(_currentRadarAngle));
}else if (_endAngle > 180.f && _endAngle < 270.f){
_endXPos = _polygonCenter.x - fabs(-(_currentRadius * ZFSin(_currentRadarAngle)));
_endYPos = _polygonCenter.y - fabs(_currentRadius * ZFCos(_currentRadarAngle));
}
// [bezier addLineToPoint:CGPointMake(_endXPos, _endYPos)];
//记录下一个item开始角度
_startAngle = _endAngle;
[self.describePointArray addObject:[NSValue valueWithCGPoint:CGPointMake(_endXPos, _endYPos)]];
}
// [bezier closePath];
// return bezier;
}
说明:getDescribePoint函数为原fill函数的改写,只保存各个顶点的坐标。
- (void)getExtendPoint {
NSInteger count = self.describePointArray.count;
for (int i=0; i<count; i++) {
[self.extendPointArray addObject:self.describePointArray[i]];
CGPoint point = [self.describePointArray[i] CGPointValue];
CGPoint pointNext = [self.describePointArray[(i+1) % count] CGPointValue];
CGFloat newX = point.x + (pointNext.x - point.x)/2.0;
CGFloat newY = point.y + (pointNext.y - point.y)/2.0;
CGPoint newPoint = CGPointMake(newX, newY);
[self.extendPointArray addObject:[NSValue valueWithCGPoint:newPoint]];
}
}
说明:getExtendPoint函数将最初的顶点数组扩展为2倍点的数组
- (CAShapeLayer *)drawTraiangleWithPoint:(CGPoint)point nextPoint:(CGPoint)nextPoint fillColor:(UIColor *)color{
// 三角形
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.fillColor = color.CGColor;
shapeLayer.strokeColor = nil;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineWidth = 1;
UIBezierPath *triangle = [UIBezierPath bezierPath];
[triangle moveToPoint:_polygonCenter];
[triangle addLineToPoint:point];
[triangle addLineToPoint:nextPoint];
[triangle closePath];
shapeLayer.path = triangle.CGPath;
if (_isAnimated) {
CABasicAnimation * fillAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
fillAnimation.duration = _animationDuration;
fillAnimation.fillMode = kCAFillModeForwards;
fillAnimation.removedOnCompletion = NO;
fillAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
fillAnimation.fromValue = (__bridge id)[self noFill].CGPath;
fillAnimation.toValue = (__bridge id)triangle.CGPath;
[shapeLayer addAnimation:fillAnimation forKey:@"animationDuration"];
}
return shapeLayer;
}
说明: drawTraiangleWithPoint:函数使用UIBezierPath绘制路径,使用CAShapeLayer显示效果,使用CABasicAnimation添加绘制过程动画。由于是绘制彩色雷达图,轮廓颜色与内部填充颜色一致,故不需要单独再绘制一次轮廓。如有需要可参考如下代码(drawTraiangleWithPoint函数的微调):
- (CAShapeLayer *)drawTraiangleStrokeWithPoint:(CGPoint)point nextPoint:(CGPoint)nextPoint strokeColor:(UIColor *)color{
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.fillColor = nil;
shapeLayer.strokeColor = color.CGColor;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.lineWidth = 1;
UIBezierPath *triangle = [UIBezierPath bezierPath];
[triangle moveToPoint:_polygonCenter];
[triangle addLineToPoint:point];
[triangle addLineToPoint:nextPoint];
[triangle closePath];
shapeLayer.path = triangle.CGPath;
if (_isAnimated) {
CABasicAnimation * fillAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
fillAnimation.duration = _animationDuration;
fillAnimation.fillMode = kCAFillModeForwards;
fillAnimation.removedOnCompletion = NO;
fillAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
fillAnimation.fromValue = (__bridge id)[self noFill].CGPath;
fillAnimation.toValue = (__bridge id)triangle.CGPath;
[shapeLayer addAnimation:fillAnimation forKey:@"animationDuration"];
}
return shapeLayer;
}
最后便是调用位置的问题了:
- (void)strokePath{
[self removeAllSubLayers];
[self getDescribePoint];
[self getExtendPoint];
NSInteger count = self.extendPointArray.count;
for (NSInteger i=0; i<count; i++) {
CGPoint point = [self.extendPointArray[(i+1) % count] CGPointValue];
CGPoint nextPoint = [self.extendPointArray[(i+2) % count] CGPointValue];
UIColor *color = self.extendColorArray[i];
[self.layer addSublayer:[self drawTraiangleWithPoint:point nextPoint:nextPoint fillColor:color]];
// [self.layer addSublayer:[self drawTraiangleStrokeWithPoint:point nextPoint:nextPoint strokeColor:color]];
}
}
说明:strokePath函数为原函数对外的接口,此处不作修改,仅仅去除了原先的2个传统的绘制函数的调用。
结尾:主要代码和思路已交代完毕,具体代码点击这里。
网友评论