iOS雷達圖 iOS RadarChart實現

VerFEL 8年前發布 | 13K 次閱讀 IOS iOS開發 移動開發

iOS雷達圖 iOS RadarChart實現

實現效果


剛拿到設計稿的時候大概看了一眼,當時心里想著放張背景圖,然后計算下相應點的坐標,在最上面畫一層就OK了,其實一開始實現的時候也確實是這么做的,然后我就日了狗了,發現設計稿上多層五邊形的間隔不是相等的,也就是說繼續按照之前的想法進行實現就要計算出每層頂點的坐標,那樣的話代碼估計會被坐標值霸屏了。好吧,推倒重來。

一層一層的分析這個需求,首先是五邊形的繪制,我創建了一個UIBezierPath的category。具體的代碼如下,其中第一個方法是用來畫各頂點不規律的五邊形的,而第二個方法是用來畫那幾個背景五邊形,兩個方法中的length都指的的中心點到各頂點的距離,第三個方法則是用來將距離轉換成具體坐標。

+ (CGPathRef)drawPentagonWithCenter:(CGPoint)center Length:(double)length
{
    NSArray *lengths = [NSArray arrayWithObjects:@(length),@(length),@(length),@(length),@(length), nil];
    return [self drawPentagonWithCenter:center LengthArray:lengths];
}

+ (CGPathRef)drawPentagonWithCenter:(CGPoint)center LengthArray:(NSArray *)lengths
{
    NSArray *coordinates = [self converCoordinateFromLength:lengths Center:center];

    UIBezierPath *bezierPath = [UIBezierPath bezierPath];
    for (int i = 0; i < [coordinates count]; i++) {
        CGPoint point = [[coordinates objectAtIndex:i] CGPointValue];
        if (i == 0) {
            [bezierPath moveToPoint:point];
        } else {
            [bezierPath addLineToPoint:point];
        }
    }
    [bezierPath closePath];

    return bezierPath.CGPath;
}

+ (NSArray *)converCoordinateFromLength:(NSArray *)lengthArray Center:(CGPoint)center
{
    NSMutableArray *coordinateArray = [NSMutableArray array];
    for (int i = 0; i < [lengthArray count] ; i++) {
        double length = [[lengthArray objectAtIndex:i] doubleValue];
        CGPoint point = CGPointZero;
        if (i == 0) {
            point =  CGPointMake(center.x - length * sin(M_PI / 5.0),
                                 center.y - length * cos(M_PI / 5.0));
        } else if (i == 1) {
            point = CGPointMake(center.x + length * sin(M_PI / 5.0),
                                center.y - length * cos(M_PI / 5.0));
        } else if (i == 2) {
            point = CGPointMake(center.x + length * cos(M_PI / 10.0),
                                center.y + length * sin(M_PI / 10.0));
        } else if (i == 3) {
            point = CGPointMake(center.x,
                                center.y +length);
        } else {
            point = CGPointMake(center.x - length * cos(M_PI / 10.0),
                                center.y + length * sin(M_PI / 10.0));
        }

        [coordinateArray addObject:[NSValue valueWithCGPoint:point]];
    }
    return coordinateArray;
}

至于最頂層數據五邊形的動畫繪制,我做了兩種實現(因為他們也還沒確定用哪個),額,怎么解釋兩者的區別呢,一種是按照與各邊成比例的速度放大,一種是按照各邊同樣的速度放大。兩種方法我都放上來:

#pragma mark - 描繪分數五邊行  按照與各邊成比例的速度放大
- (void)drawScorePentagonV
{
    NSArray *lengthsArray = [self convertLengthsFromScore:self.scoresArray];
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    pathAnimation.fromValue = (id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0) Length:0];
    pathAnimation.toValue = (id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0) LengthArray:lengthsArray];
    pathAnimation.duration = 0.75;
    pathAnimation.autoreverses = NO;
    pathAnimation.repeatCount = 0;
    pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];

    [self.shapeLayer addAnimation:pathAnimation forKey:@"scale"];
    self.shapeLayer.path = [UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0) LengthArray:lengthsArray];
    [self.layer addSublayer:self.shapeLayer];

    [self performSelector:@selector(changeBgSizeFinish) withObject:nil afterDelay:0.75];
}

#pragma mark - 描繪分數五邊行  按照各邊同樣的速度放大
- (void)drawScorePentagonV
{
    NSArray *scoresArray = [self analysisScoreArray:self.scoresArray];
    NSMutableArray *lengthsArray = [NSMutableArray array];
    [lengthsArray addObject:(id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 231 / 2.0) Length:0.0]];
    for (int i = 0; i < [scoresArray count]; i++) {
        NSArray *scores = [scoresArray objectAtIndex:i];
        [lengthsArray addObject:(id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 231 / 2.0) LengthArray:[self convertLengthsFromScore:scores]]];
    }
    CAKeyframeAnimation *frameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
    frameAnimation.values = lengthsArray;
    frameAnimation.keyTimes = [self analysisDurationArray:self.scoresArray];
    frameAnimation.duration = 2;
    frameAnimation.calculationMode = kCAAnimationLinear;

    [self.shapeLayer addAnimation:frameAnimation forKey:@"scale"];
    self.shapeLayer.path = [UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 231 / 2.0) LengthArray:[self convertLengthsFromScore:[scoresArray lastObject]]];
    [self.layer addSublayer:self.shapeLayer];


    [self performSelector:@selector(changeBgSizeFinish) withObject:nil afterDelay:2];
}

接下來就是在動畫結束的時候,將頂點的幾個小圖標加上去,沒錯,就是上面出現過的changeBgSizeFinish方法。

#pragma mark - 描點
- (void)changeBgSizeFinish
{
    NSArray *array = [self convertLengthsFromScore:self.scoresArray];
    NSArray *lengthsArray = [UIBezierPath converCoordinateFromLength:array Center:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0)];
    for (int i = 0; i < [lengthsArray count]; i++) {
        CGPoint point = [[lengthsArray objectAtIndex:i] CGPointValue];
        RADotView *dotV = [[RADotView alloc] init];
        dotV.dotColor = [UIColor colorWithHex:0xF86465];
        dotV.center = point;
        dotV.bounds = CGRectMake(0, 0, 8, 8);
        [self addSubview:dotV];
    }
}

到這里整個需求就實現了,至于幾個文字的Label,我沒想到好的辦法,都是通過量具體的坐標放到指定的位置上面的。好吧,我知道大家都很忙也比較喜歡偷懶,把剩余的相關代碼也貼上來,大家也順便幫我看看代碼是否有錯誤的地方。

#pragma mark - 分數轉換
- (NSNumber *)convertLengthFromScore:(double)score
{
    if (score >= 4) {
        return @(12 + 22 + 30 + 30);
    } else if (score >= 3){
        return @(12 + 22 + 30 + 30 * (score - 3));
    } else if (score >= 2) {
        return @(12 + 22 + 30 * (score - 2));
    } else if (score >= 1) {
        return @(12 + 22 * (score - 1));
    } else  {
        return @(12 * score);
    }
}

- (NSArray *)convertLengthsFromScore:(NSArray *)scoreArray
{
    NSMutableArray *lengthArray = [NSMutableArray array];
    for (int i = 0; i < [scoreArray count]; i++) {
        double score = [[scoreArray objectAtIndex:i] doubleValue];
        [lengthArray addObject:[self convertLengthFromScore:score]];
    }
    return lengthArray;
}

#pragma mark - 對分數進行排序(第二種動畫方法需要)
- (NSArray *)sortMergeScoresArray:(NSArray *)scores
{
    NSMutableDictionary *dic = [NSMutableDictionary dictionary];
    for (int i = 0; i < [scores count]; i++) {
        [dic setObject:@"scoresValue" forKey:[scores objectAtIndex:i]];
    }

    NSMutableArray *sortArray = [NSMutableArray arrayWithArray:dic.allKeys];
    for (int i = 0; i < [sortArray count] - 1; i++) {
        for (int j = 0; j < [sortArray count] - i - 1 ; j++) {
            if ([[sortArray objectAtIndex:j] doubleValue] > [[sortArray objectAtIndex:j + 1] doubleValue]) {
                [sortArray exchangeObjectAtIndex:j withObjectAtIndex:j + 1];

            }
        }
    }
    return  sortArray;
}

- (NSArray *)analysisDurationArray:(NSArray *)scores
{
    NSMutableArray *analysisArray = [NSMutableArray array];
    NSArray *sortArray = [self sortMergeScoresArray:scores];
    double lastProportion = 0;
    [analysisArray addObject:@(0)];
    for (int i = 0; i < [sortArray count]; i++) {
        double currentProportion = [[sortArray objectAtIndex:i] doubleValue] / [[sortArray lastObject] doubleValue];
        [analysisArray addObject:@(currentProportion)];
         lastProportion = currentProportion;
    }
    return analysisArray;
}

- (NSArray *)analysisScoreArray:(NSArray *)scores
{
    NSArray *sortArray = [self sortMergeScoresArray:scores];

    NSMutableArray *analysisArray = [NSMutableArray array];

    for (int i = 0; i < [sortArray count]; i++) {
        double stepScore = [[sortArray objectAtIndex:i] doubleValue];
        NSMutableArray *analysisScores = [NSMutableArray array];
        for (int j = 0; j < [scores count]; j++) {
            double score = [[scores objectAtIndex:j] doubleValue];
            if (stepScore > score) {
                [analysisScores addObject:@(score)];
            } else {
                [analysisScores addObject:@(stepScore)];
            }
        }
        [analysisArray addObject:analysisScores];
    }
    return analysisArray;
}

最后,總結一下這次的需求實現過程,敲代碼之前一定一定要很仔細很仔細的分析一下需求,一定一定一定要。

如果你覺得這篇文章對你有一定的幫助,請點擊一下下面的喜歡按鈕,當然如果有問題也可以大家一起討論。

 via:http://www.jianshu.com/p/4df4d4c15012
 

 本文由用戶 VerFEL 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!