iOS彩色雷达图的绘制

雷达图多用在游戏人物属性分布、成绩分布、个人画像等诸多场景。传统的雷达图分为单个对象和多个对象,前者由一组连续的点构成一个面,分别设置这个面的内部填充颜色和轮廓渲染颜色即可,后者则是多个面的叠加,原理相同。
由于工作需要,需求方设计了一个分块的雷达图,在GitHub上发现一个不错的基础版本ZFChart(在此感谢此道友),修改了并添加了一些属性和方法,制作了如下的实现形式:

iOS彩色雷达图的绘制_第1张图片
彩色雷达图
iOS彩色雷达图的绘制_第2张图片
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;
}

这段代码描述了单个对象雷达图的创建、数据源和代理函数的实现。彩色雷达图也基于同样的实现方式,具体上则是,修改了其内部的
三个子类:


iOS彩色雷达图的绘制_第3张图片
图片.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

说明: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

说明:strokePath函数为原函数对外的接口,此处不作修改,仅仅去除了原先的2个传统的绘制函数的调用。

结尾:主要代码和思路已交代完毕,具体代码点击这里。

你可能感兴趣的:(iOS彩色雷达图的绘制)