美文网首页
iOS彩色雷达图的绘制

iOS彩色雷达图的绘制

作者: Brave1991 | 来源:发表于2017-09-02 00:08 被阅读0次

    雷达图多用在游戏人物属性分布、成绩分布、个人画像等诸多场景。传统的雷达图分为单个对象和多个对象,前者由一组连续的点构成一个面,分别设置这个面的内部填充颜色和轮廓渲染颜色即可,后者则是多个面的叠加,原理相同。
    由于工作需要,需求方设计了一个分块的雷达图,在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个传统的绘制函数的调用。

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

    相关文章

      网友评论

          本文标题:iOS彩色雷达图的绘制

          本文链接:https://www.haomeiwen.com/subject/nkkljxtx.html