美文网首页
PNChart图表库学习笔记 - 折线图、柱形图、饼图

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

作者: 远山风渡月 | 来源:发表于2019-04-23 10:43 被阅读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]);
    }
    
    以上,后续有其他的再补充,如有不正确的欢迎告知哦。

    相关文章

      网友评论

          本文标题:PNChart图表库学习笔记 - 折线图、柱形图、饼图

          本文链接:https://www.haomeiwen.com/subject/oarmgqtx.html