美文网首页
基于iOS-Echarts封装的条形图绘制组件

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

作者: MichealXXX | 来源:发表于2019-06-04 18:15 被阅读0次

    现在很多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

    相关文章

      网友评论

          本文标题:基于iOS-Echarts封装的条形图绘制组件

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