基于iOS-Echarts封装的条形图绘制组件

2019-06-04  本文已影响0人  MichealXXX

现在很多app中的数据统计功能都会使用到柱状图折线图等来进行数据的展示,可能有一些比较牛的大神会选择自己绘制,至于我嘛,还是老老实实的用网上开源的工具吧,先把我写的Demo给大家放在这里,可以对照着下面的讲解看。

在做这个功能之前我上网查了一些别人的实现方式,一种是使用开源组件ios-charts,这个是使用swift开发的组件,可以直接在iOS项目中进行集成使用,我用这个也实现了功能所需的效果,但是我总感觉这个东西不好封装,有兴趣的可以去试试。

另一种是使用百度的开源图表工具ECharts,不过不太幸运的是这个组件是使用JS来写的,有JS功底的呢就可以直接来使用。针对这个问题有一位活雷锋出现了,Pluto-Y对ECharts进行了封装,名字叫做iOS-Echarts,也就是我实现功能所使用的组件。

虽然这位活雷锋为我们铺好了前期的道路,但是这个组件没有注释,这个问题就相当严重了,给大家简单的看一下Pluto-Y的demo中柱状图效果。

再给大家看一下这个简单的柱状图的实现代码

+ (PYOption *)basicBarOption {
    return [PYOption initPYOptionWithBlock:^(PYOption *option) {
        option.titleEqual([PYTitle initPYTitleWithBlock:^(PYTitle *title) {
            title.textEqual(@"世界人口总量")
            .subtextEqual(@"数据来自网络");
        }])
        .gridEqual([PYGrid initPYGridWithBlock:^(PYGrid *grid) {
            grid.xEqual(@40).x2Equal(@50);
        }])
        .tooltipEqual([PYTooltip initPYTooltipWithBlock:^(PYTooltip *tooltip) {
            tooltip.triggerEqual(PYTooltipTriggerAxis);
        }])
        .legendEqual([PYLegend initPYLegendWithBlock:^(PYLegend *legend) {
            legend.dataEqual(@[@"2011年", @"2012年"]);
        }])
        .toolboxEqual([PYToolbox initPYToolboxWithBlock:^(PYToolbox *toolbox) {
            toolbox.showEqual(YES)
            .featureEqual([PYToolboxFeature initPYToolboxFeatureWithBlock:^(PYToolboxFeature *feature) {
                feature.markEqual([PYToolboxFeatureMark initPYToolboxFeatureMarkWithBlock:^(PYToolboxFeatureMark *mark) {
                    mark.showEqual(YES);
                }])
                .dataViewEqual([PYToolboxFeatureDataView initPYToolboxFeatureDataViewWithBlock:^(PYToolboxFeatureDataView *dataView) {
                    dataView.showEqual(YES).readOnlyEqual(NO);
                }])
                .magicTypeEqual([PYToolboxFeatureMagicType initPYToolboxFeatureMagicTypeWithBlock:^(PYToolboxFeatureMagicType *magicType) {
                    magicType.showEqual(YES).typeEqual(@[PYSeriesTypeLine, PYSeriesTypeBar]);
                }])
                .restoreEqual([PYToolboxFeatureRestore initPYToolboxFeatureRestoreWithBlock:^(PYToolboxFeatureRestore *restore) {
                    restore.showEqual(YES);
                }]);
            }]);
        }])
        .calculableEqual(YES)
        .addXAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            axis.typeEqual(PYAxisTypeValue)
            .boundaryGapEqual(@[@0, @0.01]);
        }])
        .addYAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            axis.typeEqual(PYAxisTypeCategory)
            .addDataArr(@[@"巴西",@"印尼",@"美国",@"印度",@"中国",@"世界人口(万)"]);
        }])
        .addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            series.nameEqual(@"2011年")
            .typeEqual(PYSeriesTypeBar)
            .addDataArr(@[@18203, @23489, @29034, @104970, @131744, @630230]);
        }])
        .addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            series.nameEqual(@"2012年")
            .typeEqual(PYSeriesTypeBar)
            .addDataArr(@[@19325, @23438, @31000, @121594, @134141, @681807]);
        }]);
    }];
}

我第一次看简直毫无头绪,再历经了两天的不断尝试以及查询百度的JS文档,总算是稍稍的理解了其中的一部分实现方式。

言归正传,先给大家看一下我所实现的界面效果图。

常规柱状图

这是一个最为常规的条形图,也叫柱状图,我们先简单的看一下它的实现代码。

#import "SimpleBarChartViewController.h"
//引入开源库的头文件
#import <iOS_Echarts/iOS-Echarts.h>
//这是我自己封装后写的一个类
#import "ZRChartsHelper.h"

@interface SimpleBarChartViewController ()
//定义一个PYEchartsView,这个是图表绘制的view
@property (nonatomic, strong)PYEchartsView *zrSimpleChartsView;

@end

接着我们为这个view进行相应的布局

//布局chartsView
- (void)chartsViewLayout{
    self.zrSimpleChartsView = [[PYEchartsView alloc] initWithFrame:CGRectMake(0, getRectNavAndStatusHight, [UIScreen mainScreen].bounds.size.width, 350)];
    self.zrSimpleChartsView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:self.zrSimpleChartsView];
    
    NSArray *chart1Array = @[@"56",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89"];
   
    NSArray *titleArray = @[@"人员1",@"人员2",@"人员3",@"人员4",@"人员5",@"人员6",@"人员7",@"人员8",@"人员9",@"人员10",@"人员11"];
    
    //为内容进行渲染
    ZRChartsHelper *helper = [[ZRChartsHelper alloc] init];
    [helper setZRSimpleBarChartView:self.zrSimpleChartsView barValues:chart1Array xValues:titleArray];
    
}

绘制的主要代码我都写到了ZRChartsHelper这个类中,我们再看一下这个最基础简单的条形图的实现代码。

- (void)setZRSimpleBarChartView:(PYEchartsView *)chartView barValues:(NSArray *)barValues xValues:(NSArray *)xvals{
//初始化一个Option,对其属性进行设置,来达到我们想要的效果
    PYOption *option = [PYOption initPYOptionWithBlock:^(PYOption *option) {
        
        option.tooltipEqual([PYTooltip initPYTooltipWithBlock:^(PYTooltip *tooltip) {
            tooltip.triggerEqual(PYTooltipTriggerAxis)
            .axisPointerEqual([PYAxisPointer initPYAxisPointerWithBlock:^(PYAxisPointer *axisPoint) {
                axisPoint.typeEqual(PYAxisPointerTypeShadow);
            }]);
        }])
        
        //这个属性是对图表下方的文字控件进行设置
        .legendEqual([PYLegend initPYLegendWithBlock:^(PYLegend *legend) {
            //文字的内容
            legend.dataEqual(@[@"新增事件"]);
            //文字控件的纵坐标
            legend.yEqual(@300);
        }])
        //这个属性是对整个图表的位置进行设置
        .gridEqual([PYGrid initPYGridWithBlock:^(PYGrid *grid) {
            //第一个40为X轴距离左边的距离,第二个x2为X轴末端距离view右面边界的距离
            grid.xEqual(@40).x2Equal(@50);
            //图表距离顶部的距离
            grid.yEqual(@10);
            //图表的高度设置
            grid.heightEqual(@250);
        }])
        //这个属性是设置图表可左右滑动,很多情况下可能X轴要展示很多数据,因此会产生堆积,加了这个便可以左右滑动条形图来查看数据
        .dataZoomEqual([PYDataZoom initPYDataZoomWithBlock:^(PYDataZoom *dataZoom) {
            dataZoom.yEqual(@335);
            dataZoom.heightEqual(@10);
            //设置为显示滚动栏
            dataZoom.showEqual(YES)
           //下面这两个属性是设置界面一开始展示那一部分的内容,这里为图表30%~70%间的内容
            .startEqual(@30)
            .endEqual(@70);
        }])
        
        //设置工具栏,这个我没有让它进行显示,因此show这个属性我设置为隐藏,大家可以去看看有很多功能
        .toolboxEqual([PYToolbox initPYToolboxWithBlock:^(PYToolbox *toolbox) {
            //设置为隐藏
            toolbox.showEqual(NO)
            .orientEqual(PYOrientVertical)
            .xEqual(PYPositionRight)
            .yEqual(PYPositionCenter)
            .featureEqual([PYToolboxFeature initPYToolboxFeatureWithBlock:^(PYToolboxFeature *feature) {
                feature.markEqual([PYToolboxFeatureMark initPYToolboxFeatureMarkWithBlock:^(PYToolboxFeatureMark *mark) {
                    mark.showEqual(YES);
                }])
                .dataViewEqual([PYToolboxFeatureDataView initPYToolboxFeatureDataViewWithBlock:^(PYToolboxFeatureDataView *dataView) {
                    dataView.showEqual(YES).readOnlyEqual(NO);
                }])
                .magicTypeEqual([PYToolboxFeatureMagicType initPYToolboxFeatureMagicTypeWithBlock:^(PYToolboxFeatureMagicType *magicType) {
                    magicType.showEqual(YES).typeEqual(@[PYSeriesTypeLine, PYSeriesTypeBar, @"stack", @"tiled"]);
                }])
                .restoreEqual([PYToolboxFeatureRestore initPYToolboxFeatureRestoreWithBlock:^(PYToolboxFeatureRestore *restore) {
                    restore.showEqual(YES);
                }]);
            }]);
        }])
        .calculableEqual(NO)
        //设置X轴title,有多少个就在数组中写入多少个
        .addXAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            axis.typeEqual(PYAxisTypeCategory)
             //这里在我封装过后本应写为xvals,方便大家理解我直接填写了数组进去
            .addDataArr(@[@"人员1",@"人员2",@"人员3",@"人员4",@"人员5",@"人员6",@"人员7",@"人员8",@"人员9",@"人员10",@"人员11"]);
        }])
        
        
        //设置Y轴title,一般默认是数字
        .addYAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            //Y轴显示的值类型,这里为直接显示数据值
            axis.typeEqual(PYAxisTypeValue);
            //Y轴的位置,这里是在左边
            axis.positionEqual(PYPositionLeft);
        }])
        
        //这里设置柱子的属性
        .addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            //这个值可以随意起名称,他不会显示在界面中,但是这个值很重要,我会在接下来复杂的柱状图中说明
            series.stackEqual(@"事件类型")
            //柱子的名称,与上方我们设置过的表格下方的控件相对应
            .nameEqual(@"新增事件")
            //类型,bar为柱状图,如果设置为line则显示为折线
            .typeEqual(PYSeriesTypeBar)
            //设置柱子的样式
            .itemStyleEqual([PYItemStyle initPYItemStyleWithBlock:^(PYItemStyle *itemStyle) {
                itemStyle.normalEqual([PYItemStyleProp initPYItemStylePropWithBlock:^(PYItemStyleProp *normal) {
                    normal.labelEqual([PYLabel initPYLabelWithBlock:^(PYLabel *label) {
                        //是否显示柱子的数值,以及显示的位置
                        label.showEqual(YES).positionEqual(@"inside");
                    }]);
                }]);
            }])
            //设置柱子的数值,X轴有多少个单位,这个数组就要对应有多少值,封装后这里应填写barValues
           .dataEqual(@[@"56",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89"]);
        }]);
        
    }];
    
    //为负责渲染的view设置渲染option
    [chartView setOption:option];
    //加载图表渲染
    [chartView loadEcharts];
}
以上就是一个最简单的条形图的设置,其实还好不算复杂,但是产品经理怎么会这么轻松的放过你。

接下来就给大家看一下我司产品经理的需求实现图。

堆积柱状图

怎么样,这个的设置就有点复杂了,其实说复杂也没有很复杂,掌握规律就好了,我们再来看一下这个的实现代码。

//布局chartsView
- (void)chartsViewLayout{
    self.zrchartsView = [[PYEchartsView alloc] initWithFrame:CGRectMake(0, getRectNavAndStatusHight, [UIScreen mainScreen].bounds.size.width, 350)];
    self.zrchartsView.backgroundColor = [UIColor grayColor];
    [self.view addSubview:self.zrchartsView];
    //这个数组的结构是数组中嵌套数组,大家可以使用别的数据格式来进行封装
    NSArray *chart1Array = @[
                             @[@"56",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89"],
                             @[@"34",@"46",@"26",@"46",@"26",@"46",@"26",@"46",@"26",@"46",@"26"],
                             @[@"37",@"25",@"24",@"25",@"24",@"25",@"24",@"25",@"24",@"25",@"24"],
                             @[@"98",@"56",@"35",@"56",@"35",@"56",@"35",@"56",@"35",@"56",@"35"]
                             ];
    //X轴的数据
    NSArray *titleArray = @[@"人员1",@"人员2",@"人员3",@"人员4",@"人员5",@"人员6",@"人员7",@"人员8",@"人员9",@"人员10",@"人员11"];
    
    //为内容进行渲染
    ZRChartsHelper *helper = [[ZRChartsHelper alloc] init];
    [helper setZRStackBarChartView:self.zrchartsView barValues:chart1Array xValues:titleArray];
    
}

开始还是一样的,我们先布局,然后使用helper来进行界面的渲染,再来看一下stackbar的实现代码。

- (void)setZRStackBarChartView:(PYZoomEchartsView *)chartView barValues:(NSArray *)barValues xValues:(NSArray *)xvals{
    PYOption *option = [PYOption initPYOptionWithBlock:^(PYOption *option) {
        
        option.tooltipEqual([PYTooltip initPYTooltipWithBlock:^(PYTooltip *tooltip) {
            tooltip.triggerEqual(PYTooltipTriggerAxis)
            .axisPointerEqual([PYAxisPointer initPYAxisPointerWithBlock:^(PYAxisPointer *axisPoint) {
                axisPoint.typeEqual(PYAxisPointerTypeShadow);
            }]);
        }])
        
      
        .legendEqual([PYLegend initPYLegendWithBlock:^(PYLegend *legend) {
            legend.dataEqual(@[@"已处理",@"待销项",@"已销项",@"新增事件"]);
            legend.yEqual(@300);
        }])
        .gridEqual([PYGrid initPYGridWithBlock:^(PYGrid *grid) {
            grid.xEqual(@40).x2Equal(@50);
            grid.yEqual(@10);
            grid.heightEqual(@250);
        }])
        
        .dataZoomEqual([PYDataZoom initPYDataZoomWithBlock:^(PYDataZoom *dataZoom) {
            dataZoom.yEqual(@335);
            dataZoom.heightEqual(@10);
            dataZoom.showEqual(YES)
            .startEqual(@30)
            .endEqual(@70);
        }])
        
      
        .toolboxEqual([PYToolbox initPYToolboxWithBlock:^(PYToolbox *toolbox) {
            toolbox.showEqual(NO)
            .orientEqual(PYOrientVertical)
            .xEqual(PYPositionRight)
            .yEqual(PYPositionCenter)
            .featureEqual([PYToolboxFeature initPYToolboxFeatureWithBlock:^(PYToolboxFeature *feature) {
                feature.markEqual([PYToolboxFeatureMark initPYToolboxFeatureMarkWithBlock:^(PYToolboxFeatureMark *mark) {
                    mark.showEqual(YES);
                }])
                .dataViewEqual([PYToolboxFeatureDataView initPYToolboxFeatureDataViewWithBlock:^(PYToolboxFeatureDataView *dataView) {
                    dataView.showEqual(YES).readOnlyEqual(NO);
                }])
                .magicTypeEqual([PYToolboxFeatureMagicType initPYToolboxFeatureMagicTypeWithBlock:^(PYToolboxFeatureMagicType *magicType) {
                    magicType.showEqual(YES).typeEqual(@[PYSeriesTypeLine, PYSeriesTypeBar, @"stack", @"tiled"]);
                }])
                .restoreEqual([PYToolboxFeatureRestore initPYToolboxFeatureRestoreWithBlock:^(PYToolboxFeatureRestore *restore) {
                    restore.showEqual(YES);
                }]);
            }]);
        }])
        .calculableEqual(NO)
        //设置X轴title,有多少个就在数组中写入多少个
        .addXAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            axis.typeEqual(PYAxisTypeCategory)
            .addDataArr(xvals);
        }])
        
        
        //设置Y轴title,一般默认是数字
        .addYAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            axis.typeEqual(PYAxisTypeValue);
            axis.positionEqual(PYPositionLeft);
        }])
        
        
        //这个地方设置X轴每个单位中有几个柱状图,每个柱状图有几层

        //******这里的设置就是重点******//
        //大家可以看到,下面进行了四项设置,他们的nameEqual这个属性名称都不一样,但是stackEqual这个属性的内容都一样,这样就会实现我们所要的堆积效果
        .addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            series.stackEqual(@"事件类型")
            .nameEqual(@"已处理")
            .typeEqual(PYSeriesTypeBar)
            .itemStyleEqual([PYItemStyle initPYItemStyleWithBlock:^(PYItemStyle *itemStyle) {
                itemStyle.normalEqual([PYItemStyleProp initPYItemStylePropWithBlock:^(PYItemStyleProp *normal) {
                    normal.labelEqual([PYLabel initPYLabelWithBlock:^(PYLabel *label) {
                        label.showEqual(YES).positionEqual(@"inside");
                    }]);
                }]);
            }])
            .dataEqual(barValues[0]);
        }])
        .addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            series.stackEqual(@"事件类型")
            .nameEqual(@"待销项")
            .typeEqual(PYSeriesTypeBar)
            .itemStyleEqual([PYItemStyle initPYItemStyleWithBlock:^(PYItemStyle *itemStyle) {
                itemStyle.normalEqual([PYItemStyleProp initPYItemStylePropWithBlock:^(PYItemStyleProp *normal) {
                    normal.labelEqual([PYLabel initPYLabelWithBlock:^(PYLabel *label) {
                        label.showEqual(YES).positionEqual(@"inside");
                    }]);
                }]);
            }])
            .dataEqual(barValues[1]);
            
        }])
        .addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            series.stackEqual(@"事件类型")
            .nameEqual(@"已销项")
            .typeEqual(PYSeriesTypeBar)
            .itemStyleEqual([PYItemStyle initPYItemStyleWithBlock:^(PYItemStyle *itemStyle) {
                itemStyle.normalEqual([PYItemStyleProp initPYItemStylePropWithBlock:^(PYItemStyleProp *normal) {
                    normal.labelEqual([PYLabel initPYLabelWithBlock:^(PYLabel *label) {
                        label.showEqual(YES).positionEqual(@"inside");
                    }]);
                }]);
            }])
            .dataEqual(barValues[2]);
        }])
        .addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            series.stackEqual(@"事件类型")
            .nameEqual(@"新增事件")
            .typeEqual(PYSeriesTypeBar)
            .dataEqual(barValues[3]);
        }]);
        
    }];
    
    [chartView setOption:option];
    [chartView loadEcharts];
}

可以看到实现柱状图的效果的关键就是stackEqualnameEqual这两个属性,大家可以尝试一下,设置几个不同的stackEqual,柱状图就会呈现一个X轴对应多个柱子的效果,具体的效果以及代码我都写在Demo中了,Demo会在文章最下面的地址中给大家下载。

其实以上两个界面的实现ios-charts这个组件也可以轻松的做到,我觉得不太方便的地方就是,这个组件面对混合图表的设置看起来有那么一丝丝不太友好,导致我回去钻研了两天ECharts

混合图表

这种条形图加折线图的混合显示图表还是花了我一点时间去看文档
的,先去看了一下JS代码中如何设置双Y轴,再回到iOS的项目中试试能不能找到设置双Y轴JS同名属性,如何设置坐标的显示格式等等,所以推荐大家遇到自己不太知道的实现方式时,去看看百度文档中的JS代码的设置,再去ECharts中寻找同名属性去试试,我也是慢慢试出来的。

关键实现代码:

//Y轴的设置变成一个数组,装入了两个Y轴
.addYAxisArr(@[[PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            axis.typeEqual(PYAxisTypeValue);
            //位置靠左
            axis.positionEqual(PYPositionLeft);
            
        }],[PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
            axis.typeEqual(PYAxisTypeValue);
            //位置靠右
            axis.positionEqual(PYPositionRight);
            //显示格式为百分比
            axis.axisLabel.formatterEqual(@"{value} %");
        }]])
[PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
            series.stackEqual(@"事件类型");
            series.nameEqual(@"销项率");
            series.yAxisIndexEqual(@(1))
            //设置类型为Line(折线)
            .typeEqual(PYSeriesTypeLine)
            .dataEqual(lineValues)
            .itemStyleEqual([PYItemStyle initPYItemStyleWithBlock:^(PYItemStyle *itemStyle) {
                itemStyle.normalEqual([PYItemStyleProp initPYItemStylePropWithBlock:^(PYItemStyleProp *normal) {
                    normal.borderColorEqual([PYColor colorWithHexString:@"#fff"])
                    .borderWidthEqual(@2)
                    //设置折线上label显示的内容
                    .labelEqual([PYLabel initPYLabelWithBlock:^(PYLabel *label) {
                        //  折线内容显示位置
                        label.positionEqual(@"inside")
                        //显示为百分比
                        .formatterEqual(@"{c}%")
                        //文字颜色
                        .textStyleEqual([PYTextStyle initPYTextStyleWithBlock:^(PYTextStyle *textStyle) {
                            textStyle.colorEqual([PYColor colorWithHexString:@"#fff"]);
                        }]);
                    }]);
                }]);
            }]);
        }]]);

以上就是几种柱状图的实现方式,应该够大部分场景使用了,我的demo中还封装了简单的饼状图环状图,有新的图表效果封装我会持续更新demo,大家对照我的博客和demo理解了之后完全可以针对自己的项目做更好的封装,我这个为了赶工可能有点粗糙,如果有帮到你就帮我点个赞就好啦。

Demo地址:ZRChartsHelper

上一篇下一篇

猜你喜欢

热点阅读