PNChart圖表庫學習筆記 - 折線圖、柱形圖、餅圖

單位項目中需要用到圖表類的功能,因此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]);
}
以上,后續有其他的再補充,如有不正確的歡迎告知哦。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。