iOS彩色雷達圖的繪制

雷達圖多用在游戲人物屬性分布、成績分布、個人畫像等諸多場景。傳統(tǒng)的雷達圖分為單個對象和多個對象,前者由一組連續(xù)的點構(gòu)成一個面,分別設(shè)置這個面的內(nèi)部填充顏色和輪廓渲染顏色即可,后者則是多個面的疊加,原理相同。
由于工作需要,需求方設(shè)計了一個分塊的雷達圖,在GitHub上發(fā)現(xiàn)一個不錯的基礎(chǔ)版本ZFChart(在此感謝此道友),修改了并添加了一些屬性和方法,制作了如下的實現(xiàn)形式:

彩色雷達圖
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;
}

這段代碼描述了單個對象雷達圖的創(chuàng)建、數(shù)據(jù)源和代理函數(shù)的實現(xiàn)。彩色雷達圖也基于同樣的實現(xiàn)方式,具體上則是,修改了其內(nèi)部的
三個子類:


圖片.png

這里先說下思路:

  • 1.獲取各個頂點的坐標pointArray
  • 2.將pointArray擴展成2倍頂點個數(shù)的extendArray
  • 3.每次從extendArray中取出2個連續(xù)的點,與雷達圖圓心共同構(gòu)成一個三角形
  • 4.使用預(yù)先設(shè)置好的顏色數(shù)組,填充每次構(gòu)成的三角形
  • 5.設(shè)置一個從初始到完成的動畫效果

說明: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函數(shù)為原fill函數(shù)的改寫,只保存各個頂點的坐標。

- (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函數(shù)將最初的頂點數(shù)組擴展為2倍點的數(shù)組

- (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:函數(shù)使用UIBezierPath繪制路徑,使用CAShapeLayer顯示效果,使用CABasicAnimation添加繪制過程動畫。由于是繪制彩色雷達圖,輪廓顏色與內(nèi)部填充顏色一致,故不需要單獨再繪制一次輪廓。如有需要可參考如下代碼(drawTraiangleWithPoint函數(shù)的微調(diào)):

- (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;
}

最后便是調(diào)用位置的問題了:

- (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函數(shù)為原函數(shù)對外的接口,此處不作修改,僅僅去除了原先的2個傳統(tǒng)的繪制函數(shù)的調(diào)用。

結(jié)尾:主要代碼和思路已交代完畢,具體代碼點擊這里。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容