现在很多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];
}
可以看到实现柱状图的效果的关键就是stackEqual
,nameEqual
这两个属性,大家可以尝试一下,设置几个不同的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