單位項目中需要用到圖表類的功能,因此Github找了一些,以PNChart為例講一下使用過程。
先看一下我實現的效果。
柱形圖和餅圖:
折線圖
@property (nonatomic, strong) PNLineChart * lineChart;
// 初始化折線圖
- (void)initializeLineChart {
_lineChart = [[PNLineChart alloc] initWithFrame:CGRectMake(0, kNavHeight+55, SCREEN_WIDTH, SCREEN_WIDTH-100)];
_lineChart.backgroundColor = [UIColor clearColor];
// 整個圖表的寬度和高度,默認自動計算
// chartCavanWidth和chartCavanHeight組成圖表內容的可顯示區域(包含坐標軸,但不包含坐標軸的刻度值)
//_lineChart.chartCavanWidth = SCREEN_WIDTH-60;
//_lineChart.chartCavanHeight = SCREEN_WIDTH-100;
}
標準折線圖
- (void)loadBasicLineChart {
[self initializeLineChart];
// 是否顯示 xy 坐標軸(默認不顯示)
_lineChart.showCoordinateAxis = YES;
// 坐標軸寬度
_lineChart.axisWidth = 1;
// 坐標軸顏色
_lineChart.axisColor = RGBColor(153, 153, 153);
// 設置Y軸坐標值的最大值、最小值
_lineChart.yFixedValueMax = 520;
_lineChart.yFixedValueMin = 400;
// Y軸相關設置
// 是否顯示垂直于Y軸的橫向虛線
_lineChart.showYGridLines = YES;
// 橫向虛線顏色
_lineChart.yGridLinesColor = SeparatorLineColor_Darker;
// 軸單位
_lineChart.yUnit = @"人數";
// 刻度值內容格式(不設置或者設置為@"%1.f"為整型,@"%1.1f"為小數點后一位)
//_lineChart.yLabelFormat = @"%1.1f";
// 刻度值字體顏色
_lineChart.yLabelColor = RGBColor(102, 102, 102);
// 刻度值字體大小
_lineChart.yLabelFont = [UIFont systemFontOfSize:10];
// 設置軸數據 如果需要設置刻度值字體大小和顏色,在設置軸數據前設置,否則無效
//[_lineChart setYLabels:@[@"400", @"420", @"440", @"460", @"480", @"500"]];
// X軸相關設置
_lineChart.xUnit = @"年限";
_lineChart.xLabelColor = RGBColor(102, 102, 102);
_lineChart.xLabelFont = [UIFont systemFontOfSize:10];
// 設置x軸的數據
[_lineChart setXLabels:@[@"2010", @"2011", @"2012", @"2013", @"2014", @"2015", @"2016", @"2017", @"2018"]];
// 設置每個點的y值
// 初始化折線數據
NSArray *dataArray = @[@405, @426, @455, @461, @428, @446, @473, @496, @488];
PNLineChartData *lineData = [PNLineChartData new];
// 設置折線的顏色 & 透明度
lineData.color = PNTwitterColor;
lineData.alpha = 0.5f;
// 設置折線的寬度
lineData.lineWidth = 2.0;
// 設置折線的點數
lineData.itemCount = dataArray.count;
// 設置是否展示折點的Label
//lineData.showPointLabel = YES;
// 設置折點的大小
//lineData.inflexionPointWidth = 4.0f;
// 設置折點的文本顏色
//lineData.pointLabelColor = RGBColor(102, 102, 102);
// 設置折點的文本字體
//lineData.pointLabelFont = [UIFont systemFontOfSize:12];
// 設置折點的樣式
//lineData.inflexionPointStyle = PNLineChartPointStyleCircle;
// 這個block的作用是將上面的dataArray里的每一個值傳給line chart。
lineData.getData = ^(NSUInteger index) {
CGFloat yValue = [dataArray[index] floatValue];
return [PNLineChartDataItem dataItemWithY:yValue];
};
// 數據添加到圖表中
_lineChart.chartData = @[lineData];
// 是否展示平滑線條(默認為NO)
//self.lineChart.showSmoothLines = YES;
//繪制圖表
[_lineChart strokeChart];
//設置代理,響應點擊
_lineChart.delegate = self;
// 添加至視圖
[self.view addSubview:_lineChart];
}
但是運行結果后你可能會發現,圖表中每一項數據與對應的X軸坐標值有或大或小的出入或者X軸坐標值不在刻度線正下方而是兩個相鄰刻度線的中間(具體與數據項多少有關)
在網上看到了別人的建議和解決辦法,是需要修改源碼。
在PNLineChart.m文件的 setXLabels: withWidth: 方法中(大約在179行)需要進行修改:
if (_showLabel) {
for (int index = 0; index < xLabels.count; index++) {
labelText = xLabels[index];
/* 源碼
NSInteger x = (index * _xLabelWidth + _chartMarginLeft + _xLabelWidth / 2.0);
NSInteger y = _chartMarginBottom + _chartCavanHeight;
*/
NSInteger x = (2*_x_axisMarginLeft + (index * _xLabelWidth)) - _xLabelWidth/2;
NSInteger y = _chartMarginTop + _chartCavanHeight;
PNChartLabel *label = [[PNChartLabel alloc] initWithFrame:CGRectMake(x, y, (NSInteger) _xLabelWidth, (NSInteger) _chartMarginBottom)];
[label setTextAlignment:NSTextAlignmentCenter];
label.text = labelText;
[self setCustomStyleForXLabel:label];
[self addSubview:label];
[_xChartLabels addObject:label];
}
}
個人理解如下:
_chartMarginLeft是控制圖表繪制內容與圖表整體視圖的左間距,但源碼中計算X軸坐標label的frame也使用了_chartMarginLeft,即如果通過_chartMarginLeft來調節,圖表內容與X軸坐標label的位置都會發生改變。所以我在PNLineChart.h文件中增加了兩項值,通過x_axisMarginLeft來代替_chartMarginLeft控制坐標label的位置:
// 用于在計算X軸坐標軸指示線和坐標值文本位置時區分chartMarginLeft和chartMarginRight
@property (nonatomic) CGFloat x_axisMarginLeft;
@property (nonatomic) CGFloat x_axisMarginRight;
在PNLineChart.m文件的 setupDefaultValues 初始化方法中,將其初始值設置為與_chartMarginLeft的初始值一致:
// add
_x_axisMarginLeft = 25.0;
_x_axisMarginRight = 25.0;
在PNLineChart.m文件的 drawRect: 方法中(大約在663行),刻度線的位置計算中也有_chartMarginLeft這一項,修改為通過_x_axisMarginLeft來控制刻度線的位置,保證刻度線始終在坐標值label的正上方:
// draw x axis separator
CGPoint point;
for (NSUInteger i = 0; i < [self.xLabels count]; i++) {
/* 源碼
point = CGPointMake(2 * _chartMarginLeft + (i * _xLabelWidth), _chartMarginBottom + _chartCavanHeight);
*/
// 修改軸刻度線的位置
point = CGPointMake(2 * _x_axisMarginLeft + (i * _xLabelWidth), _chartMarginBottom + _chartCavanHeight);
CGContextMoveToPoint(ctx, point.x, point.y - 2);
CGContextAddLineToPoint(ctx, point.x, point.y);
CGContextStrokePath(ctx);
}
// draw y axis separator
CGFloat yStepHeight = _chartCavanHeight / _yLabelNum;
Y軸刻度線與刻度值位置不匹配也可以在這個方法中修改(// draw y axis separator 下面那一部分代碼)
坐標軸單位名稱
X軸的單位名稱label原位置在軸線箭頭右端,我修改源碼(同樣是 drawRect: 方法中)將X軸的單位名稱label移到了軸線右端箭頭的上方來方便顯示,而且源碼中限制的單位名稱label長度較短,只能顯示兩個文字,此處也做了修改:
// 此處可修改軸單位名稱字體大小
UIFont *font = [UIFont systemFontOfSize:11];
// draw y unit
if ([self.yUnit length]) {
/* 源碼整體內容
CGFloat height = [PNLineChart sizeOfString:self.yUnit withWidth:30.f font:font].height;
CGRect drawRect = CGRectMake(_chartMarginLeft + 10 + 5, 0, 30.f, height);
[self drawTextInContext:ctx text:self.yUnit inRect:drawRect font:font];
*/
/**添加后整體內容**/
// 增加寬度最大值 防止單位較長顯示不全
CGFloat height = [PNLineChart sizeOfString:self.yUnit withWidth:50.f font:font].height;
CGFloat width = [PNLineChart sizeOfString:self.yUnit withWidth:50.f font:font].width;
CGRect drawRect = CGRectMake(_x_axisMarginLeft + 10 + 5, 0, width, height);
[self drawTextInContext:ctx text:self.yUnit inRect:drawRect font:font];
/**添加后整體內容**/
}
// draw x unit
if ([self.xUnit length]) {
/* 源碼
CGFloat height = [PNLineChart sizeOfString:self.xUnit withWidth:30.f font:font].height;
CGRect drawRect = CGRectMake(CGRectGetWidth(rect) - _chartMarginLeft + 5, _chartMarginBottom + _chartCavanHeight - height / 2, 25.f, height);
[self drawTextInContext:ctx text:self.xUnit inRect:drawRect font:font];
*/
/**添加后整體內容**/
CGFloat height = [PNLineChart sizeOfString:self.xUnit withWidth:50.f font:font].height;
CGFloat width = [PNLineChart sizeOfString:self.xUnit withWidth:50.f font:font].width;
// 將X軸單位位置移到箭頭的上方 -5是為了防止label底部緊貼坐標軸
CGRect drawRect = CGRectMake(CGRectGetWidth(rect) - _chartMarginRight - width - 5, _chartMarginBottom + _chartCavanHeight - height - 5, width, height);
[self drawTextInContext:ctx text:self.xUnit inRect:drawRect font:font];
/**添加后整體內容**/
}
修改完成后,在適配圖表內容顯示時,根據需要改變_x_axisMarginLeft和chartMarginLeft這兩項的值即可。
在之前的圖表創建方法中修改如下:
- (void)loadBasicLineChart {
[self initializeLineChart];
// 圖標可顯示區域右邊距
// X軸箭頭過于靠近最后一個刻度值,可調節此項值
_lineChart.chartMarginRight = 0;
// 圖表可顯示區域左邊距
// 圖表內容與X軸內容對應有偏差,可調節此項或_x_axisMarginLeft
_lineChart.chartMarginLeft = 32;
// 是否顯示 xy 坐標軸(默認不顯示)
_lineChart.showCoordinateAxis = YES;
// 坐標軸寬度
_lineChart.axisWidth = 1;
結果如圖:多條曲線圖
屬于折線圖的一種,增加一個LineChartData,如何添加圖例詳看代碼:
- (void)loadSmoothLineChart {
[self initializeLineChart];
_lineChart.chartMarginRight = 5;
_lineChart.chartMarginLeft = 15;
_lineChart.chartCavanWidth = SCREEN_WIDTH-20;
_lineChart.chartCavanHeight = SCREEN_WIDTH-150;
_lineChart.x_axisMarginLeft = 17;
// Y軸相關設置
_lineChart.yLabelNum = 8;
_lineChart.yLabelColor = [UIColor clearColor];
_lineChart.yLabelFont = [UIFont systemFontOfSize:1];
_lineChart.showYGridLines = YES;
_lineChart.yGridLinesColor = SeparatorLineColor_Darker;
// X軸相關設置
_lineChart.xLabelColor = RGBColor(153, 153, 153);
_lineChart.xLabelFont = [UIFont systemFontOfSize:10];
[_lineChart setXLabels:@[@"一月", @"二月", @"三月", @"四月", @"五月", @"六月", @"七月", @"八月", @"九月", @"十月"]];
// 折線數據
NSArray *firstDataArray = @[@772, @956, @755, @791, @912, @1053, @1386, @1279, @1048, @885];
PNLineChartData *firstData = [PNLineChartData new];
firstData.color = PNGreen;
firstData.alpha = 0.7f;
firstData.lineWidth = 2.0;
firstData.itemCount = firstDataArray.count;
firstData.showPointLabel = YES;
firstData.inflexionPointWidth = 5.0f;
firstData.pointLabelColor = RGBColor(102, 102, 102);
firstData.pointLabelFont = [UIFont systemFontOfSize:10];
firstData.inflexionPointStyle = PNLineChartPointStyleSquare;
firstData.getData = ^(NSUInteger index) {
CGFloat yValue = [firstDataArray[index] floatValue];
return [PNLineChartDataItem dataItemWithY:yValue];
};
NSArray *secondDataArray = @[@1012, @1053, @922, @879, @953, @998, @1186, @1129, @1086, @951];
PNLineChartData *secondData = [PNLineChartData new];
secondData.color = PNBlue;
secondData.alpha = 0.7f;
secondData.lineWidth = 2.0;
secondData.itemCount = firstDataArray.count;
secondData.showPointLabel = YES;
secondData.inflexionPointWidth = 5.0f;
secondData.pointLabelColor = RGBColor(102, 102, 102);
secondData.pointLabelFont = [UIFont systemFontOfSize:10];
secondData.inflexionPointStyle = PNLineChartPointStyleTriangle;
secondData.getData = ^(NSUInteger index) {
CGFloat yValue = [secondDataArray[index] floatValue];
return [PNLineChartDataItem dataItemWithY:yValue];
};
_lineChart.chartData = @[firstData,secondData];
_lineChart.showSmoothLines = YES;
// 繪制圖表
[_lineChart strokeChart];
// 設置代理,響應點擊
_lineChart.delegate = self;
// 添加至視圖
[self.view addSubview:_lineChart];
//添加圖例
firstData.dataTitle =@"A產品銷量 ";
secondData.dataTitle =@"B產品銷量";
//橫向顯示
_lineChart.legendStyle = PNLegendItemStyleSerial;
// legendFontColor只能用系統規定顏色,其他方法獲取顏色不可使用,會crash報錯
_lineChart.legendFontColor = [UIColor darkGrayColor];
_lineChart.legendFont = [UIFont systemFontOfSize:12.0];
//圖例所在位置
UIView *legend = [_lineChart getLegendWithMaxWidth:220];
[legend setFrame:CGRectMake(SCREEN_WIDTH/2-110, kNavHeight+55+(SCREEN_WIDTH-100)+5, legend.frame.size.width, legend.frame.size.height)];
//顯示比例
_lineChart.hasLegend = YES;
//顯示位置
_lineChart.legendPosition = PNLegendPositionBottom;
[self.view addSubview:legend];
}
可滑動的折線圖
有的時候為了美觀,每一項數據之間間隔較大,但數據項較多,需要將圖標設計為可滑動樣式,將圖表創建在ScrollView上,通過數據項多少計算contentSize:
- (void)loadSwiperLineChart {
NSArray *x_axis = @[@"03-01", @"03-02", @"03-03", @"03-04", @"03-05", @"03-06", @"03-07", @"03-08", @"03-09", @"03-10", @"03-11", @"03-12", @"03-13", @"03-14", @"03-15"];
CGFloat maxWidth = MAX(15 + x_axis.count * 60.0 + 15, SCREEN_WIDTH-50);
self.chartBGScrollView.contentSize = CGSizeMake(maxWidth, self.chartBGScrollView.frame.size.height);
_lineChart = [[PNLineChart alloc] initWithFrame:CGRectMake(0, 0, self.chartBGScrollView.contentSize.width, self.chartBGScrollView.frame.size.height-35)];
_lineChart.backgroundColor = [UIColor clearColor];
_lineChart.chartMarginLeft = 15;
_lineChart.chartMarginRight = 15;
_lineChart.x_axisMarginLeft = 22;
// Y軸相關設置
_lineChart.yLabelColor = [UIColor clearColor];
_lineChart.yLabelFont = [UIFont systemFontOfSize:1];
// X軸相關設置
_lineChart.xLabelColor = RGBColor(153, 153, 153);
_lineChart.xLabelFont = [UIFont systemFontOfSize:10];
[_lineChart setXLabels:x_axis];
// 折線數據
NSArray *firstDataArray = @[@5, @8, @10, @6, @4, @1, @-3, @-1, @0, @1, @3, @5, @4, @0, @1];
PNLineChartData *firstData = [PNLineChartData new];
firstData.color = RGBColor(69, 140, 240);
firstData.lineWidth = 1.0;
firstData.itemCount = firstDataArray.count;
firstData.showPointLabel = YES;
firstData.inflexionPointWidth = 3.0f;
firstData.pointLabelColor = RGBColor(102, 102, 102);
firstData.pointLabelFont = [UIFont systemFontOfSize:10];
firstData.pointLabelFormat = @"%1.f°";
firstData.inflexionPointStyle = PNLineChartPointStyleCircle;
firstData.getData = ^(NSUInteger index) {
CGFloat yValue = [firstDataArray[index] floatValue];
return [PNLineChartDataItem dataItemWithY:yValue];
};
_lineChart.chartData = @[firstData];
_lineChart.showSmoothLines = YES;
// 繪制圖表
[_lineChart strokeChart];
// 設置代理,響應點擊
_lineChart.delegate = self;
// 添加至視圖
[_chartBGScrollView addSubview:_lineChart];
// [self loadWeatherIconImgView];
}
柱形圖
@property (nonatomic, strong) PNBarChart * barChart;
- (void)loadBarChart {
// 初始化
_barChart = [[PNBarChart alloc] initWithFrame:CGRectMake(0, kNavHeight+30, SCREEN_WIDTH, SCREEN_WIDTH-100)];
_barChart.backgroundColor = [UIColor clearColor];
// 是否顯示xy軸的刻度值(默認不顯示)
_barChart.showLabel = YES;
// 是否顯示坐標軸(默認不顯示)
_barChart.showChartBorder = YES;
// 柱子圓角值
_barChart.barRadius = 4;
// 柱子寬度值
_barChart.barWidth = 32;
// 柱子渲染顏色(柱子實體顏色)
_barChart.strokeColor = RGBColor(90, 144, 245);
// 柱子上是否顯示數值(默認為YES)
_barChart.isShowNumbers = NO;
// 柱子是否立體顯示(有漸變色效果,默認為YES)
_barChart.isGradientShow = NO;
_barChart.labelTextColor = RGBColor(102, 102, 102);
// 這部分設置盡量放在前面 放在后面可能導致坐標軸的刻度值不顯示
// Y軸坐標值的寬度
_barChart.yChartLabelWidth = 20;
// Y軸距離圖表最左側的距離(左邊距)
_barChart.chartMarginLeft = 25;
// X軸最右側距離圖表最右側的距離(右邊距)
_barChart.chartMarginRight = 5;
// 柱子最頂端距離圖表最上方的距離(上邊距)
_barChart.chartMarginTop = 5;
// X軸距離圖表最底部的距離(下邊距)
_barChart.chartMarginBottom = 10;
// X坐標刻度的上邊距
_barChart.labelMarginTop = 5.0;
//設置bar Color
_barChart.xLabels = @[@"一",@"二",@"三",@"四",@"五",@"六"];
_barChart.yValues = @[@21, @16, @10, @24, @26, @14];
_barChart.yLabelFormatter = ^NSString*(CGFloat yLabelValue) {
return[NSString stringWithFormat:@"%f", yLabelValue];
};
//開始繪制
[_barChart strokeChart];
[self.view addSubview:_barChart];
}
餅圖
@property (nonatomic, strong) PNPieChart * pieChart;
- (void)loadPieChart {
NSArray*items = @[
[PNPieChartDataItem dataItemWithValue:231 color:PNBlue description:@"支付寶 "],
[PNPieChartDataItem dataItemWithValue:198 color:PNGreen description:@"微信 "],
[PNPieChartDataItem dataItemWithValue:112 color:PNStarYellow description:@"信用卡 "],
[PNPieChartDataItem dataItemWithValue:137 color:PNLightBlue description:@"現金 "],
[PNPieChartDataItem dataItemWithValue:90 color:PNPinkDark description:@"其它"]];
PNPieChart *pieChart = [[PNPieChart alloc] initWithFrame:CGRectMake(70, kNavHeight+55, SCREEN_WIDTH-140, SCREEN_WIDTH-140) items:items];
// 餅圖描述文字
pieChart.descriptionTextColor = [UIColor whiteColor];
// 設置文字字體
pieChart.descriptionTextFont = [UIFont fontWithName:@"Avenir-Medium" size:14];
// 文字陰影顏色 默認不顯示陰影
// pieChart.descriptionTextShadowColor = [UIColor redColor];
// 顯示實際數值,不顯示實際比例
pieChart.showAbsoluteValues = NO;
// 只顯示數值,不顯示內容描述
pieChart.showOnlyValues = NO;
// showPullLine 顯示指示線 新版本PNChart已取消
// 內外圓大小,此處設置無作用,需要修改源碼或者新建類繼承PNPieChart,類中重寫recompute方法
//pieChart.innerCircleRadius = 0;
//pieChart.outerCircleRadius = 0;
[pieChart strokeChart];
//加到父視圖上
[self.view addSubview:pieChart];
//顯示比例
pieChart.hasLegend= YES;
//橫向顯示
pieChart.legendStyle= PNLegendItemStyleSerial;
pieChart.legendFontColor = [UIColor lightGrayColor]; // 不可以用RGB計算 只能使用系統指定顏色
pieChart.legendFont= [UIFont boldSystemFontOfSize:14];
//顯示位置
pieChart.legendPosition= PNLegendPositionTop;
//獲得圖例,當橫向排布不下另起一行
UIView*legend = [pieChart getLegendWithMaxWidth:SCREEN_WIDTH-70];
legend.frame= CGRectMake(35,kNavHeight+60+(SCREEN_WIDTH-140)+10, legend.bounds.size.width, legend.bounds.size.height);
[self.view addSubview:legend];
}
內外圓大小的設置。有些餅圖要求展示效果為實心圓,這里我選擇直接修改源碼,也可以像注釋中建議的繼承類再重寫方法:
在PNPieChart.m文件的 recompute 方法中(大約在102行)修改如下:
- (void)recompute {
/* 源碼
self.outerCircleRadius = CGRectGetWidth(self.bounds) / 2;
self.innerCircleRadius = CGRectGetWidth(self.bounds) / 6;
*/
// 設備餅圖的內部圓大小與外部圓大小 可以通通過自定義一個類繼承PieChart,在這個類中重寫recompute方法
self.outerCircleRadius = CGRectGetWidth(self.bounds) / 2;
// 設置內部空心圓為0
self.innerCircleRadius = 0;
}
效果如圖:也可以在這個方法里修改內圓的半徑大小,達到需要的展示效果。
代理方法
#pragma mark - Delegate
- (void)userClickedOnLinePoint:(CGPoint)point lineIndex:(NSInteger)lineIndex {
NSLog(@"chosed lineIndex(線 下標) %ld", lineIndex);
}
- (void)userClickedOnLineKeyPoint:(CGPoint)point lineIndex:(NSInteger)lineIndex pointIndex:(NSInteger)pointIndex {
NSLog(@"lineIndex(第幾根線):%ld 選擇點的pointIndex %ld",lineIndex + 1,pointIndex);
//NSLog(@"X:%@ Y:%@",_chart_X_LabelArr[pointIndex],_chart_Y_LabelArr[pointIndex]);
}