iOS 自定義折線圖

iOS 自定義折線圖

效果及 Demo

由于產(chǎn)品需求要展示分?jǐn)?shù)麻捻,并且60分以下展示需要壓縮比例,而開源的圖表庫太過復(fù)雜并且有些無法滿足需求呀袱,學(xué)習(xí)成本偏高贸毕,于是決定自己寫一個(gè),效果如下圖夜赵。

Demo地址

chart-w480

視圖層級及設(shè)計(jì)思路

  1. 使用UICollectionView實(shí)現(xiàn)滾動效果明棍,并且部分展示元素可以復(fù)用,如下圖中日期label寇僧、虛線及圓點(diǎn)
  2. 使用CAShapeLayer+UIBezierPath實(shí)現(xiàn)折線及陰影繪制
cell-w480

代碼實(shí)現(xiàn)

圖表實(shí)現(xiàn)代碼

#import "ZHLineChartView.h"

#import "ZHLineChartConst.h"
#import "ZHLineChartModel.h"

@interface ZHLineChartView ()

@end


@implementation ZHLineChartView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        // 這步是因?yàn)樘福衅渌?layer 時(shí),折線圖需要展示在最底層
        self.layer.zPosition = -1.f;
    }
    return self;
}

#pragma mark - Load Subviews

- (void)makeDraw {
    // x軸的 y
    CGFloat moveStartY = kChartViewH;
    
    UIBezierPath *aPath = [UIBezierPath bezierPath];//填充鍍層
    UIBezierPath *aPathLine = [UIBezierPath bezierPath];//線
    
    // 所有經(jīng)過的坐標(biāo)點(diǎn)婉宰,畫線
//    NSInteger startI = 0; // 用于計(jì)算無效點(diǎn)陰影
    BOOL ignoreZeroPoint = YES;
    for (int i = 0; i < _dataModels.count; i++) {
        CGFloat yPoint = _dataModels[i].yPoint;
        if (ignoreZeroPoint && yPoint == kInvaildYPoint) { //忽略前面為0的點(diǎn)(產(chǎn)品需求)
            continue;
        }
        CGFloat xPoint = kLineChartMoveStartX + i * kLineChartMoveW;
        if (ignoreZeroPoint) {
            //設(shè)置開始的坐標(biāo)點(diǎn)
            //取得第一個(gè)有效點(diǎn)的 index
//            startI = i;
            [aPath moveToPoint:CGPointMake(xPoint, moveStartY)]; //鍍層要從 x 軸開始畫歌豺,再添加第一個(gè)點(diǎn)坐標(biāo)
            [aPath addLineToPoint:CGPointMake(xPoint, yPoint)];
            [aPathLine moveToPoint:CGPointMake(xPoint, yPoint)];
            ignoreZeroPoint = NO; //開始畫點(diǎn),不再忽略為0的點(diǎn)
        } else {
            [aPath addLineToPoint:CGPointMake(xPoint, yPoint)];
            [aPathLine addLineToPoint:CGPointMake(xPoint, yPoint)];
        }
    }
    
    //鍍層收邊
    CGFloat lineW = kLineChartMoveStartX + (_dataModels.count - 1) * kLineChartMoveW;
    [aPath addLineToPoint:CGPointMake(lineW, moveStartY)];
    [aPath closePath];
    
    // 前面為 0 的點(diǎn)灰色蒙層心包,UI 后來又不要了
//    CAGradientLayer *grayLayer = [CAGradientLayer layer];
//    grayLayer.colors = @[(__bridge id)[[UIColor lightGrayColor] colorWithAlphaComponent:0.f].CGColor, (__bridge id)[[UIColor lightGrayColor] colorWithAlphaComponent:0.4f].CGColor];
//    grayLayer.frame = CGRectMake(kLineChartMoveStartX, 0.f, startI * kLineChartMoveW, moveStartY);
//    grayLayer.startPoint = CGPointZero;
//    grayLayer.endPoint = CGPointMake(1.f, 1.f);
//    [self.layer addSublayer:grayLayer];
    
    
    [self addGradientLayerWithPath:aPath];
    [self addLineLayerWithPath:aPathLine];
    [self addXLineLayer];
}

- (void)addXLineLayer {
    CGFloat lineW = kLineChartMoveStartX + (_dataModels.count - 1) * kLineChartMoveW;
    //x 軸
    UIBezierPath *xLinePath = [UIBezierPath bezierPath];
    [xLinePath moveToPoint:CGPointMake(kLineChartMoveStartX - kLineChartMoveW / 2.f, kChartViewH)];
    [xLinePath addLineToPoint:CGPointMake(kLineChartMoveStartX + lineW, kChartViewH)];
    
    CAShapeLayer *xLineLayer = [CAShapeLayer layer];
    xLineLayer.strokeColor = [UIColor lightGrayColor].CGColor;
    xLineLayer.lineWidth = 0.5f;
    xLineLayer.path = xLinePath.CGPath;
    xLineLayer.zPosition = -2;
    [self.layer addSublayer:xLineLayer];
}

- (void)addLineLayerWithPath:(UIBezierPath *)path {
    //折線
    CAShapeLayer *shapelayerLine = [CAShapeLayer layer];
    //設(shè)置邊框顏色类咧,就是上邊畫的,線的顏色
    shapelayerLine.strokeColor = [[UIColor orangeColor] CGColor];
    //設(shè)置填充顏色 如果不需要[UIColor clearColor]
    shapelayerLine.fillColor = [[UIColor clearColor] CGColor];
    //就是這句話在關(guān)聯(lián)彼此(UIBezierPath和CAShapeLayer):
    shapelayerLine.path = path.CGPath;
    [self.layer addSublayer:shapelayerLine];
}

- (void)addGradientLayerWithPath:(UIBezierPath *)path {
    //獲取總共的長度
    CGFloat lineW = kLineChartMoveStartX + (_dataModels.count - 1) * kLineChartMoveW;
    
    CAShapeLayer *shapelayer = [CAShapeLayer layer];
    //設(shè)置邊框顏色蟹腾,就是上邊畫的痕惋,線的顏色
    shapelayer.strokeColor = [[UIColor orangeColor] CGColor];
    //設(shè)置填充顏色
    shapelayer.fillColor = [[UIColor orangeColor] CGColor];
    //就是這句話在關(guān)聯(lián)彼此(UIBezierPath和CAShapeLayer):
    shapelayer.path = path.CGPath;
    
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.colors = @[(__bridge id)[[UIColor orangeColor] colorWithAlphaComponent:0.4f].CGColor, (__bridge id)[[UIColor orangeColor] colorWithAlphaComponent:0.f].CGColor];
    //    gradientLayer.locations = @[@0.f, @0.5f];
    gradientLayer.startPoint = CGPointMake(0.5, 0.f);
    gradientLayer.endPoint = CGPointMake(0.5, 1.f);
    gradientLayer.shadowPath = path.CGPath;
    gradientLayer.frame = CGRectMake(0, 0, lineW, kChartViewH);
    gradientLayer.mask = shapelayer;
    [self.layer addSublayer:gradientLayer];
}

#pragma mark - Setter

- (void)setDataModels:(NSArray<ZHLineChartModel *> *)dataModels {
    _dataModels = dataModels;
    
    [self makeDraw];
}


@end
#import "ZHLineChartModel.h"

#import "ZHLineChartConst.h"

@interface ZHLineChartModel ()

@property (nonatomic, strong, readwrite) NSString *date;
@property (nonatomic, strong, readwrite) NSString *score;
@property (nonatomic, assign, readwrite) CGFloat yPoint;

@end

@implementation ZHLineChartModel

#pragma mark - Helper

- (void)setupYPoint {
    //這步放在 model 中是因?yàn)榉奖隳P娃D(zhuǎn)換時(shí)異步計(jì)算 y 坐標(biāo)
    CGFloat score = [self.score intValue];
    CGFloat point = kItemTopMargin + kItemHeight / 2.f;
    
    /**
     高度計(jì)算,100-60分娃殖,按比例計(jì)算值戳,小于 60 分時(shí),小于 60 的區(qū)域按比例計(jì)算
     */
    CGFloat height = 4 * (kItemHeight + kItemSpace); // 60 - 100 的總高度
    if (score >= 60.f && score < 100) {
        height = (100.f - score) / 40.f * height; // 按比例下降高度
        point += height;
    } else if (score < 60) {
        // 小于 60 分炉爆,最后一段按比例計(jì)算 Y 值
        CGFloat tempH = kItemHeight / 2.f + kItemSpace;
        tempH = (60.f - score) / 60.f * tempH;
        point += tempH + height;
    }
    
    self.yPoint = point;
}


#pragma mark - Test

+ (NSArray <ZHLineChartModel *>*)testDataArray {
    NSArray *dateArr = @[@"08-29",@"08-30",@"08-31",@"09-01",@"09-02",@"09-03",@"09-04",@"09-05",@"09-06",@"09-07",@"09-08",@"09-09"];
    NSArray *scoreArr = @[@"20",@"60",@"40",@"55",@"100",@"80",@"90",@"70",@"77",@"30",@"80",@"88"];
    
    NSMutableArray *arr = [NSMutableArray array];
    
    for (NSInteger i = 0; i < dateArr.count; i++) {
        ZHLineChartModel *model = [[ZHLineChartModel alloc] init];
        model.score = scoreArr[i];
        model.date = dateArr[i];
        [model setupYPoint];
        
        [arr addObject:model];
    }
    
    return arr.copy;
}

@end

其他代碼詳見 Demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末堕虹,一起剝皮案震驚了整個(gè)濱河市卧晓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赴捞,老刑警劉巖逼裆,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異赦政,居然都是意外死亡胜宇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門恢着,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桐愉,“玉大人,你說我怎么就攤上這事掰派〈踊澹” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵碗淌,是天一觀的道長盏求。 經(jīng)常有香客問我,道長亿眠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任磅废,我火速辦了婚禮纳像,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拯勉。我一直安慰自己竟趾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布宫峦。 她就那樣靜靜地躺著岔帽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪导绷。 梳的紋絲不亂的頭發(fā)上犀勒,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音妥曲,去河邊找鬼贾费。 笑死,一個(gè)胖子當(dāng)著我的面吹牛檐盟,可吹牛的內(nèi)容都是我干的褂萧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼葵萎,長吁一口氣:“原來是場噩夢啊……” “哼导犹!你這毒婦竟也來了唱凯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤谎痢,失蹤者是張志新(化名)和其女友劉穎波丰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體舶得,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掰烟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沐批。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纫骑。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖九孩,靈堂內(nèi)的尸體忽然破棺而出先馆,到底是詐尸還是另有隱情,我是刑警寧澤躺彬,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布煤墙,位于F島的核電站,受9級特大地震影響宪拥,放射性物質(zhì)發(fā)生泄漏仿野。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一她君、第九天 我趴在偏房一處隱蔽的房頂上張望脚作。 院中可真熱鬧,春花似錦缔刹、人聲如沸球涛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽亿扁。三九已至,卻和暖如春鸟廓,著一層夾襖步出監(jiān)牢的瞬間从祝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工肝箱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哄褒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓煌张,卻偏偏與公主長得像呐赡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子骏融,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 8期21天E站到底第8天打卡 用條件格式扮靚報(bào)告 一.基本用法: 1.突出顯示單元格規(guī)則:在開始下方有一個(gè)"條件格...
    幽蘭_87ab閱讀 387評論 0 3
  • 第一部分:通過分列快捷鍵提取有效信息文本 一链嘀、基本用法 分隔符號 選中需要分列的數(shù)據(jù)-分列→點(diǎn)擊下一步-分隔符號(...
    sharon閃閃526閱讀 285評論 0 1
  • E戰(zhàn)到底第八天萌狂! 每天打卡學(xué)習(xí)已經(jīng)進(jìn)入第八天了,即使再忙這個(gè)打卡學(xué)習(xí)已經(jīng)成了自己生活中的一部分了1 一怀泊、條件格式的...
    楊愛鑫閱讀 381評論 0 0
  • 要和老公結(jié)婚了 老公比我大四歲 上學(xué)的時(shí)候比我高五個(gè)年級 我總問他為什么不早點(diǎn)來找我茫藏,當(dāng)然了這是個(gè)無解的問題,我們...
    生氣ing_girl閱讀 301評論 0 0
  • 是我把“白頭偕老”變成了自己的牢霹琼,總想著以此作為最后目的务傲,尋尋覓覓,絲毫不讓枣申,就算撞到頭破血流也在所不惜∈燮希現(xiàn)在想來...
    箜蒔閱讀 147評論 0 3