說明:
已將折線圖封裝到了一個UIView子類中闸溃,并提供了相應的接口鸟妙。若你遇到相應的需求可以直接將文件拖到項目中逆粹,調(diào)用相應的接口即可,感謝@
世俗孤島
X軸為前7天的時間,動態(tài)獲取日期,點擊坐標點彈出標簽
有問題聯(lián)系QQ: 652985191
Blog中涉及到的知識點
CALayer
圖層别凹,可以簡單的看做一個不接受用戶交互的UIView
每個圖層都具有一個CALayer類型mask屬性邓厕,作用與蒙版相似
Blog中主要用到的CALayer子類有
CAGradientLayer逝嚎,繪制顏色漸變的背景圖層
CAShapeLayer,繪制折線圖
CAAnimation
核心動畫的基類(不可實例化對象)详恼,實現(xiàn)動畫操作
Quartz 2D
一個二維的繪圖引擎补君,用來繪制折線(Path)和坐標軸信息(Text)
實現(xiàn)思路
折線圖視圖
整個折線圖將會被自定義到一個UIView子類中
坐標軸繪制
坐標軸直接繪制到折線圖視圖上,在自定義折線圖視圖的 drawRect 方法中繪制坐標軸相關信息(線條和文字)
注意坐標系的轉(zhuǎn)換
線條顏色漸變
失敗的方案
開始的時候昧互,為了實現(xiàn)線條顏色漸變挽铁,我的思考方向是,如何改變路徑(UIBezierPath)的渲染顏色(strokeColor)敞掘。但是strokeColor只可以設置一種叽掘,所以最終無法實現(xiàn)線條顏色的漸變。
成功的方案
在探索過程中找到了CALayer的CALayer類型的mask()屬性渐逃,最終找到了解決方案够掠,即:使用UIView對象封裝漸變背景視圖(frame為折線圖視圖的減去坐標軸后的frame),創(chuàng)建一個CAGradientLayer漸變圖層添加到背景視圖上茄菊。
創(chuàng)建一個CAShapeLayer對象疯潭,用于繪制線條赊堪,線條的渲染顏色(strokeColor)為whiteColor,填充顏色(fillColor)為clearColor竖哩,從而顯示出漸變圖層的顏色哭廉。將CAShapeLayer對象設置為背景視圖的mask屬性,即背景視圖的蒙版相叁。
折線
使用 UIBezierPath 類來繪制折線
折線轉(zhuǎn)折處尖角的處理遵绰,使用 kCALineCapRound 與 kCALineJoinRound 設置折線轉(zhuǎn)折處為圓角
折線起點與終點的圓點的處理,可以直接在 UIBezierPath 對象上添加一個圓增淹,設置遠的半徑為路徑寬度的一半椿访,從而保證是一個實心的圓而不是一個圓環(huán)
折線轉(zhuǎn)折處的點
折線轉(zhuǎn)折處點使用一個類來描述(不使用CGPoint的原因是:折線轉(zhuǎn)折處的點需要放到一個數(shù)組中)
坐標軸信息
X軸、Y軸的信息分別放到一個數(shù)組中
X軸顯示的是最近七天的日期虑润,Y軸顯示的是最近七天數(shù)據(jù)變化的幅度
動畫
使用CABasicAnimation類來完成繪制折線圖時的動畫
需要注意的是成玫,折線路徑在一開始時需要社會線寬為0,開始繪制時才設置為適當?shù)木€寬拳喻,保證一開折線路徑是隱藏的
標簽
點擊視圖哭当,向折線圖視圖上添加一個標簽(UIButton對象),顯示折線點的信息
具體實現(xiàn)
折線轉(zhuǎn)折處的點
使用一個類來描述折線轉(zhuǎn)折處的點冗澈,代碼如下:
#import
@interfaceHLQLineChartPoint :NSObject
@property(nonatomic,assign)CGFloatx;
@property(nonatomic,assign)CGFloaty;
+ (instancetype)pointWithX:(CGFloat)X andY:(CGFloat)Y;
@end
#import"HLQLineChart.h"
@implementationHLQLineChartPoint
+ (instancetype)pointWithX:(CGFloat)X andY:(CGFloat)Y
{
HLQLineChartPoint*point = [[selfalloc]init];
point.x= X;
point.y= Y;
returnpoint;
}
@end
自定義折線圖視圖
折線圖視圖是一個自定義的UIView子類钦勘,代碼如下:
@interfaceHLQLineChart :UIView
//是否點擊彈出每個點的數(shù)值
@property(nonatomic,assign,getter=isPopupView)BOOLpopupView;
//是否倒序繪制圖形,默認為正序
@property(nonatomic,assign,getter=isInvertedOrder)BOOLinvertedOrder;
//繪制圖形的時間,默認是1秒
@property(nonatomic,assign)CGFloatdrawTime;
//初始化折線視圖
- (instancetype)initWithFrame:(CGRect)frame andCoordinateFigureArray:(NSMutableArray*)coordinateFigureArray
andYAxleArray:(NSMutableArray*)YAxleArray andBackgroudColor:(UIColor*)backgroudColor andFirstChangeColor:(UIColor*)FirstChangeColorandSecondChangeColor:(UIColor*)SecondChangeColor andKFontColor:(UIColor*)fontColor andcolorGradient:(BOOL)isColorGradient;
//開始繪制折線
- (void)startDrawLineChart;
@end
//初始化
- (instancetype)initWithFrame:(CGRect)frame andCoordinateFigureArray:(NSMutableArray*)coordinateFigureArray andYAxleArray:(NSMutableArray*)YAxleArray andBackgroudColor:(UIColor*)backgroudColor andFirstChangeColor:(UIColor*)FirstChangeColorandSecondChangeColor:(UIColor*)SecondChangeColor andKFontColor:(UIColor*)fontColor andcolorGradient:(BOOL)isColorGradient
{
if(coordinateFigureArray) {
self.pointArray= coordinateFigureArray;
}
if(YAxleArray) {
self.yAxisInformationArray= YAxleArray;
}
if(self= [superinitWithFrame:frame]) {
//設置折線圖的背景顏色
if(backgroudColor) {
self.backgroundColor= backgroudColor;
}else{
self.backgroundColor=KLineBackgroudColor;
}
if(fontColor) {
self.fontColor= fontColor;
}else{
self.fontColor=KFontColor;
}
if(isColorGradient) {
self.colorGradient= isColorGradient;
}
/**設置漸變背景視圖*/
[selfdrawGradientBackgroundViewWithFirstChangeColor:FirstChangeColorandSecondChangeColor:SecondChangeColor];
/**設置折線圖層*/
[selfsetupLineChartLayerAppearance];
}
returnself;
}
繪制坐標軸信息
- (void)drawRect:(CGRect)rect {
CGContextRefref =UIGraphicsGetCurrentContext();
//x軸信息
[self.xAxisInformationArrayenumerateObjectsUsingBlock:^(NSString*_Nonnullobj,NSUIntegeridx,BOOL*_Nonnullstop) {
NSMutableDictionary*dictM = [NSMutableDictionarydictionary];
UIFont*font = [UIFontsystemFontOfSize:8];
dictM[NSFontAttributeName] =font;
dictM[NSForegroundColorAttributeName] =self.fontColor;
CGSizeinformationSize = [objsizeWithAttributes:dictM];
//計算繪制起點
CGFloatdrawStartPointX =KPadding+idx*self.xAxisSpacing+(self.xAxisSpacing-informationSize.width)*0.5;
CGFloatdrawStartPointY =self.bounds.size.height-KPadding+(KPadding-informationSize.height)*0.5;
[objdrawAtPoint:CGPointMake(drawStartPointX, drawStartPointY)withAttributes:dictM];
}];
//y軸
[self.yAxisInformationArrayenumerateObjectsUsingBlock:^(NSString*_Nonnullobj,NSUIntegeridx,BOOL*_Nonnullstop) {
NSMutableDictionary*dictM = [NSMutableDictionarydictionary];
UIFont*font = [UIFontsystemFontOfSize:8];
dictM[NSFontAttributeName] =font;
dictM[NSForegroundColorAttributeName] =KFontColor;
CGSizeinformationSize = [objsizeWithAttributes:dictM];
//計算繪制起點
CGFloatdrawStartPointX = (KPadding-informationSize.width)*0.5;
CGFloatdrawStartPointY =self.bounds.size.height-KPadding-idx*self.yAxisSpacing-informationSize.height*0.5;
[objdrawAtPoint:CGPointMake(drawStartPointX, drawStartPointY)withAttributes:dictM];
//橫向的標線
CGContextSetRGBStrokeColor(ref,KRedFloat,KGreenFloat,KBlueFloat,KAlaphFloat);
CGContextSetLineWidth(ref,kCoordinateLineWith);
CGContextMoveToPoint(ref,KPadding,self.bounds.size.height-KPadding-(idx*self.yAxisSpacing));
CGContextAddLineToPoint(ref,self.bounds.size.width,self.bounds.size.height-KPadding-(idx*self.yAxisSpacing));
CGContextStrokePath(ref);
}];
}
動畫的開始與結(jié)束
- (void)startDrawLineChart
{
//self.lineChartLayer.lineWidth = 1.0;
if(self.isDrawing) {
return;
}
if(self.tapButton) {
[self.tapButtonremoveFromSuperview];
}
self.lineChartLayer.fillColor= [UIColorclearColor].CGColor;
CABasicAnimation*pathAnimation;
//設置動畫的相關屬性
if(self.isInvertedOrder) {
pathAnimation = [CABasicAnimationanimationWithKeyPath:@"strokeStart"];
}else{
pathAnimation = [CABasicAnimationanimationWithKeyPath:@"strokeEnd"];
}
if(self.drawTime) {
pathAnimation.duration=self.drawTime;
}else{
pathAnimation.duration=1.0;
}
pathAnimation.repeatCount=1;
pathAnimation.removedOnCompletion=NO;
pathAnimation.timingFunction= [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn];
if(self.isInvertedOrder) {
pathAnimation.fromValue= [NSNumbernumberWithInt:1.0];
pathAnimation.toValue= [NSNumbernumberWithInt:0.0];
}else{
pathAnimation.fromValue= [NSNumbernumberWithInt:0.0];
pathAnimation.toValue= [NSNumbernumberWithInt:1.0];
}
pathAnimation.delegate=self;
[self.lineChartLayeraddAnimation:pathAnimationforKey:@"strokeEnd"];
}
//動畫開始
- (void)animationDidStart:(CAAnimation*)anim
{
self.Drawing=YES;
}
//動畫結(jié)束之后
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag
{
if(self.iscolorGradient) {
self.lineChartLayer.fillColor= [UIColorwhiteColor].CGColor;
}
self.Drawing=NO;
}
集成折線圖視圖
//繪制漸變背景顏色
- (void)drawGradientBackgroundViewWithFirstChangeColor:(UIColor*)FirstChangeColorandSecondChangeColor:(UIColor*)SecondChangeColor
{
self.gradientBackgroundView= [[UIViewalloc]initWithFrame:CGRectMake(KPadding,0,self.bounds.size.width-KPadding,self.bounds.size.height-KPadding)];
[selfaddSubview:self.gradientBackgroundView];
self.gradientLayer= [CAGradientLayerlayer];
self.gradientLayer.frame=self.gradientBackgroundView.bounds;
self.gradientLayer.startPoint=CGPointMake(0,0.0);
self.gradientLayer.endPoint=CGPointMake(1.0,0);
//設置顏色的漸變過程
if(FirstChangeColor&&SecondChangeColor) {
self.gradientLayerColors= [NSMutableArrayarrayWithArray:@[(__bridgeid)FirstChangeColor.CGColor,
(__bridgeid)SecondChangeColor.CGColor]];
}else{
self.gradientLayerColors= [NSMutableArrayarrayWithArray:@[(__bridgeid)KFirstChangeColor.CGColor,
(__bridgeid)KSecondChangeColor.CGColor]];
}
self.gradientLayer.colors=self.gradientLayerColors;
[self.gradientBackgroundView.layeraddSublayer:self.gradientLayer];
}
- (void)setupLineChartLayerAppearance
{
UIBezierPath*path = [UIBezierPathbezierPath];
[self.pointArrayenumerateObjectsUsingBlock:^(HLQLineChartPoint*_Nonnullobj,NSUIntegeridx,BOOL*_Nonnullstop) {
//所有的點的位置以及圓心點
CGPointallPoint =CGPointMake(self.xAxisSpacing*0.5+(obj.x-1)*self.xAxisSpacing,self.bounds.size.height-KPadding-obj.y*self.yAxisSpacing);
//從x軸起點或終點的坐標
CGPointxFromWithEndPoint =CGPointMake(self.xAxisSpacing*0.5+(obj.x-1)*self.xAxisSpacing,self.bounds.size.height-KPadding);
//折線
if(idx ==0) {
[pathmoveToPoint:xFromWithEndPoint];
[pathaddLineToPoint:allPoint];
[pathaddArcWithCenter:allPointradius:4.0startAngle:0endAngle:M_PI*2clockwise:YES];
}elseif(idx ==self.pointArray.count-1){
[pathaddArcWithCenter:allPointradius:4.0startAngle:0endAngle:M_PI*2clockwise:YES];
[pathaddLineToPoint:allPoint];
[pathaddLineToPoint:xFromWithEndPoint];
}else{
[pathaddArcWithCenter:allPointradius:4.0startAngle:0endAngle:M_PI*2clockwise:YES];
[pathaddLineToPoint:allPoint];
}
}];
self.lineChartLayer= [CAShapeLayerlayer];
self.lineChartLayer.path= path.CGPath;
self.lineChartLayer.strokeColor= [UIColorblueColor].CGColor;
if(!self.iscolorGradient) {
self.lineChartLayer.fillColor= [UIColorclearColor].CGColor;
}
self.lineChartLayer.lineWidth=2.0;
self.lineChartLayer.lineCap=kCALineCapRound;
self.lineChartLayer.lineJoin=kCALineJoinRound;
//設置折線圖層為漸變背景的mask
self.gradientBackgroundView.layer.mask=self.lineChartLayer;
}