PNChart图表库学习笔记 - 折线图、柱形图、饼图

2019-04-23  本文已影响0人  远山风渡月

单位项目中需要用到图表类的功能,因此Github找了一些,以PNChart为例讲一下使用过程。
先看一下我实现的效果。

折线图: QQ20190423-折线图-HD.gif
柱形图和饼图: QQ20190423-other-HD.gif

折线图

@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轴坐标值不在刻度线正下方而是两个相邻刻度线的中间(具体与数据项多少有关)
result-01.png

在网上看到了别人的建议和解决办法,是需要修改源码。
在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;
结果如图: result-02.png

多条曲线图

属于折线图的一种,增加一个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;
}
效果如图: result-03.png

也可以在这个方法里修改内圆的半径大小,达到需要的展示效果。

代理方法

#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]);
}
以上,后续有其他的再补充,如有不正确的欢迎告知哦。
上一篇下一篇

猜你喜欢

热点阅读