iOS雷达图 iOS RadarChart实现

VerFEL 8年前
   <p style="text-align: center;"><img src="https://simg.open-open.com/show/91c0e3c39be38898928f8551f17fea17.jpg" alt="iOS雷达图 iOS RadarChart实现" width="730" height="548"></p>    <p style="text-align: center;">实现效果</p>    <p><br> 刚拿到设计稿的时候大概看了一眼,当时心里想着放张背景图,然后计算下相应点的坐标,在最上面画一层就OK了,其实一开始实现的时候也确实是这么做的,然后我就日了狗了,发现设计稿上多层五边形的间隔不是相等的,也就是说继续按照之前的想法进行实现就要计算出每层顶点的坐标,那样的话代码估计会被坐标值霸屏了。好吧,推倒重来。</p>    <p>一层一层的分析这个需求,首先是五边形的绘制,我创建了一个UIBezierPath的category。具体的代码如下,其中第一个方法是用来画各顶点不规律的五边形的,而第二个方法是用来画那几个背景五边形,两个方法中的length都指的的中心点到各顶点的距离,第三个方法则是用来将距离转换成具体坐标。</p>    <pre>  <code class="language-objectivec">+ (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;  }</code></pre>    <p>至于最顶层数据五边形的动画绘制,我做了两种实现(因为他们也还没确定用哪个),额,怎么解释两者的区别呢,一种是按照与各边成比例的速度放大,一种是按照各边同样的速度放大。两种方法我都放上来:</p>    <pre>  <code class="language-objectivec">#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];  }</code></pre>    <p>接下来就是在动画结束的时候,将顶点的几个小图标加上去,没错,就是上面出现过的changeBgSizeFinish方法。</p>    <pre>  <code class="language-objectivec">#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];      }  }</code></pre>    <p>到这里整个需求就实现了,至于几个文字的Label,我没想到好的办法,都是通过量具体的坐标放到指定的位置上面的。好吧,我知道大家都很忙也比较喜欢偷懒,把剩余的相关代码也贴上来,大家也顺便帮我看看代码是否有错误的地方。</p>    <pre>  <code class="language-objectivec">#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;  }</code></pre>    <p>最后,总结一下这次的需求实现过程,敲代码之前一定一定要很仔细很仔细的分析一下需求,一定一定一定要。</p>    <p>如果你觉得这篇文章对你有一定的帮助,请点击一下下面的喜欢按钮,当然如果有问题也可以大家一起讨论。</p>    <p> via:http://www.jianshu.com/p/4df4d4c15012<br>  </p>