iOS雷達圖

uwzzf4535 8年前發布 | 8K 次閱讀 C語言 iOS開發 移動開發

最近公司要做一個醫學考試類的App,里面有一個能力分析的雷達圖,leader讓我來封裝這個雷達圖。這個模塊是在做測心率模塊期間完成的,也是使用的 CoreGraphics 來實現的。

之前并不知道雷達圖長什么樣,這個是我在百度上下載的。大概就長這個樣子,我們要用的和這個不太一樣,沒有文字和介紹。

思路

寫這個的時候,沒有查閱什么資料,所以不太清楚別人是怎么做的。

我是從角度出發的,例如上面的圖中,有5個能力模塊,5個能力模塊的分支(后面就叫它 主干 了,我也不知道叫什么)兩兩的夾角都是相同的。

  1. 首先把一個主干放在 y軸 上,這個主干繞 原點 O 一周是 2π ,被5個 主干 5等分,那么每一個夾角都是 2π/5 。
  2. 主干的長是相同的,可以通過 正弦 和 余弦 來求出每個主干落點的坐標,再從 原點O 到 落點 畫線。
  3. 之后的網格和能力分布的畫法也是這個思路,同樣是從 y軸 上的主干取點,不同的是 斜邊 的長度會根據 網格間距 和 能力百分比 變化,如下圖:

不知道大家還記不記得正弦和余弦是啥了,我是已經還給數學老師了(雖然初中數學也是年組第一的),剛好寫這個的前一天在看高數。

正弦: sin? = 對邊/斜邊
  余弦: cos? = 臨邊/斜邊

實現

1. 初始化,定義幾個屬性后面使用

// 元素數組
@property (strong, nonatomic) NSArray <Element *>*elements;

這是一個放Model的數組,用來儲存能力模塊信息

// 每一個主干的長度
static float element_w = 0;
// 主干到View預留的空白部分寬度
static float border_w  = 5;
// 中心點的橫坐標
static float center_w  = 0;
// 中心點的縱坐標
static float center_h  = 0;
- (instancetype)initWithFrame:(CGRect)frame Elements:(NSArray <Element *>*)elements {
    if (self = [super initWithFrame:frame]) {
        self.backgroundColor = [UIColor whiteColor];
        self.elements = elements;
        if (self.frame.size.width>self.frame.size.height)
            element_w = (self.frame.size.height-border_w*2)/2;
        else
            element_w = (self.frame.size.width-border_w*2)/2;

        center_w = self.frame.size.width/2;
        center_h = self.frame.size.height/2;

    }
    return self;
}

2.畫主干

- (void)buildRadarMap {
    if (self.elements.count<3) return;

    // 獲取畫布
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 劃線顏色
    if (self.trunkLineColor)
        CGContextSetStrokeColorWithColor(context, self.trunkLineColor.CGColor);
    else
        CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    // 劃線寬度
    if (self.lineWidth)
        CGContextSetLineWidth(context, self.lineWidth);
    else
        CGContextSetLineWidth(context, 1);
    // 起點坐標
    CGContextMoveToPoint(context, center_w, center_h);
    // 第一條線
    CGContextAddLineToPoint(context, center_w, center_h - element_w);

    //畫元素主干
    for (int i = 1; i <self.elements.count; i++) {
        float x   = 0;
        float y   = 0;
        double pi = (M_PI*2.0/(self.elements.count))*i;

        // 計算主干落點坐標
        Coordinate(pi, element_w, center_w, center_h,&x, &y);
        // 設置每次的初始點坐標
        CGContextMoveToPoint(context, center_w, center_h);
        // 設置終點坐標
        CGContextAddLineToPoint(context, x, y);
    }
    CGContextStrokePath(context);
}

注: self.trunkLineColor 是我設置的一個外部可控主干的顏色,如果尾部沒有設置就給默認值。 self.lineWidth 就是先的寬度了。

/**
 *  雷達圖分成幾等份 默認1等份(最外面一圈的框框)
 */
@property (assign, nonatomic) float part;

/**
 *  雷達線的寬度 默認1
 */
@property (assign, nonatomic) float lineWidth;

/**
 *  主干線顏色 默認黑色
 */
@property (strong, nonatomic) UIColor *trunkLineColor;

/**
 *  分等份線顏色 默認黑色
 */
@property (strong, nonatomic) UIColor *partLineColor;

/**
 *  比例線顏色 默認綠色
 */
@property (strong, nonatomic) UIColor *percentLineColor;

- (void)setTrunkLineColor:(UIColor *)trunkLineColor {
    _trunkLineColor =trunkLineColor;
    [self setNeedsDisplay];
}

- (void)setPartLineColor:(UIColor *)partLineColor {
    _partLineColor = partLineColor;
    [self setNeedsDisplay];
}

- (void)setPercentLineColor:(UIColor *)percentLineColor {
    _percentLineColor = percentLineColor;
    [self setNeedsDisplay];
}

設置set方法調用 setNeedsDisplay 外部設置顏色的時候會調用 drawRect:

3.計算落點的方法

#pragma mark - 算落點坐標

void Coordinate (double pi, float l, float c_w , float c_h, float *x, float *y) {

    if (pi < M_PI_2) {
        *x = c_w + sin(pi)*l;
        *y = c_h - cos(pi)*l;
    } else if (pi == M_PI_2) {
        *x = c_w + sin(pi)*l;
        *y = c_h + cos(pi)*l;
    } else if (pi < M_PI) {
        *x = c_w + sin(pi)*l;
        *y = c_h - cos(pi)*l;
    } else if (pi == M_PI) {
        *x = c_w + sin(pi)*l;
        *y = c_h + cos(pi)*l;
    } else if (pi < M_PI_2*3) {
        *x = c_w + sin(pi)*l;
        *y = c_h - cos(pi)*l;
    } else if (pi == M_PI_2*3) {
        *x = c_w + sin(pi)*l;
        *y = c_h + cos(pi)*l;
    } else {
        *x = c_w + sin(pi)*l;
        *y = c_h - cos(pi)*l;
    }
}
變量 用途
pi 角度
l 主干長度
c_w 原點橫坐標
c_h 原點縱坐標
x 落點橫坐標
y 落點縱坐標

坐標系和View的Rect還不太一樣,比如在坐標系第二、三象限的 y 是負值,而我們要的坐標的 y 是越往下越大,所以我們要 c_h+(-y) , x 同理。

4.畫網格,也就是要把主干分成幾等分,為了更靈活我也設置了可以從外部設置。

/**
 *  雷達圖分成幾等份 默認1等份(最外面一圈的框框)
 */
@property (assign, nonatomic) float part;

- (void)setPart:(float)part {
    _part = part;
    [self setNeedsDisplay];
}
#pragma mark - 畫雷達分等分圖

- (void)buildPart {

    float r = self.part<=1?1:self.part;

    // 獲取畫布
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 劃線顏色
    if (self.partLineColor)
        CGContextSetStrokeColorWithColor(context, self.partLineColor.CGColor);
    else
        CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    // 劃線寬度
    if (self.lineWidth)
        CGContextSetLineWidth(context, self.lineWidth);
    else
        CGContextSetLineWidth(context, 1);
    // 話分割線
    for (int j = 0; j<r; j++) {
        // 設置每次的初始點坐標
        CGContextMoveToPoint(context, center_w, border_w);
        // 畫百分比分部
        for (int i = 1; i<=self.elements.count; i++) {
            float x   = 0;
            float y   = 0;
            double pi = (M_PI*2.0/(self.elements.count))*i;
            Coordinate(pi,element_w*(r-j)/r, center_w, center_h,&x, &y);

            if (i == 1) {
                CGContextMoveToPoint(context, center_w, border_w + element_w*j/r);
            }
            if (i == self.elements.count) {
                CGContextAddLineToPoint(context, center_w, border_w + element_w*j/r);
            } else {
                CGContextAddLineToPoint(context, x, y);
            }
        }

    }
    CGContextStrokePath(context);
}

原理和畫主干是一樣的,只不過是每次的 起點 變成了上一個點的 落點 。把 主干 的長度進行了分割

5.畫能力占比線

#pragma mark - 畫百分比占比線

- (void)buildPercent {

    // 獲取畫布
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 劃線顏色
    if (self.percentLineColor)
        CGContextSetStrokeColorWithColor(context, self.percentLineColor.CGColor);
    else
        CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
    // 劃線寬度
    if (self.lineWidth)
        CGContextSetLineWidth(context, self.lineWidth);
    else
        CGContextSetLineWidth(context, 1);

    Element *ele = self.elements[0];
    CGContextMoveToPoint(context, center_w, border_w +element_w*(1-ele.percent));
    for (int i = 1; i<=self.elements.count; i++) {
        float x   = 0;
        float y   = 0;

        if (i == self.elements.count) {
            //終點,最終回到開始點坐標
            Element *ele = self.elements[0];
            CGContextAddLineToPoint(context, center_w, border_w +element_w*(1-ele.percent));
        } else {
            Element *ele = self.elements[i];
            double pi = (M_PI*2.0/(self.elements.count))*i;
            Coordinate(pi,element_w*ele.percent, center_w, center_h,&x, &y);
            CGContextAddLineToPoint(context, x, y);
        }
    }
    CGContextStrokePath(context);
}

這里和畫網格不一樣的就是每次的把主干的長度按照這個 能力值 的 百分比 分割

總結

因為有之前的心率那得基礎,這里完成的很快,然后做了一個相對比較靈活的封裝,還沒有加能力描述和百分比值,后續加上。

 

 

 

來自:http://www.jianshu.com/p/e6d65460a6c2

 

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