現(xiàn)在很多app中的數(shù)據(jù)統(tǒng)計功能都會使用到
柱狀圖
炼列,折線圖
等來進行數(shù)據(jù)的展示,可能有一些比較牛的大神會選擇自己繪制音比,至于我嘛俭尖,還是老老實實的用網(wǎng)上開源的工具吧,先把我寫的Demo給大家放在這里洞翩,可以對照著下面的講解看稽犁。在做這個功能之前我上網(wǎng)查了一些別人的實現(xiàn)方式,一種是使用開源組件ios-charts骚亿,這個是使用swift
開發(fā)的組件已亥,可以直接在iOS
項目中進行集成使用,我用這個也實現(xiàn)了功能所需的效果来屠,但是我總感覺這個東西不好封裝
虑椎,有興趣的可以去試試。
另一種是使用百度的開源圖表工具ECharts俱笛,不過不太幸運的是這個組件是使用JS
來寫的捆姜,有JS
功底的呢就可以直接來使用。針對這個問題有一位活雷鋒出現(xiàn)了嫂粟,Pluto-Y對ECharts進行了封裝娇未,名字叫做iOS-Echarts,也就是我實現(xiàn)功能所使用的組件星虹。
雖然這位活雷鋒為我們鋪好了前期的道路零抬,但是這個組件沒有注釋
镊讼,這個問題就相當嚴重了,給大家簡單的看一下Pluto-Y的demo
中柱狀圖效果平夜。
再給大家看一下這個簡單的柱狀圖的實現(xiàn)代碼
+ (PYOption *)basicBarOption {
return [PYOption initPYOptionWithBlock:^(PYOption *option) {
option.titleEqual([PYTitle initPYTitleWithBlock:^(PYTitle *title) {
title.textEqual(@"世界人口總量")
.subtextEqual(@"數(shù)據(jù)來自網(wǎng)絡(luò)");
}])
.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]);
}]);
}];
}
我第一次看簡直毫無頭緒蝶棋,再歷經(jīng)了兩天的不斷嘗試以及查詢百度的JS文檔,總算是稍稍的理解了其中的一部分實現(xiàn)方式忽妒。
言歸正傳玩裙,先給大家看一下我所實現(xiàn)的界面效果圖。
這是一個最為常規(guī)的條形圖段直,也叫柱狀圖吃溅,我們先簡單的看一下它的實現(xiàn)代碼。
#import "SimpleBarChartViewController.h"
//引入開源庫的頭文件
#import <iOS_Echarts/iOS-Echarts.h>
//這是我自己封裝后寫的一個類
#import "ZRChartsHelper.h"
@interface SimpleBarChartViewController ()
//定義一個PYEchartsView鸯檬,這個是圖表繪制的view
@property (nonatomic, strong)PYEchartsView *zrSimpleChartsView;
@end
接著我們?yōu)檫@個view
進行相應(yīng)的布局
//布局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"];
//為內(nèi)容進行渲染
ZRChartsHelper *helper = [[ZRChartsHelper alloc] init];
[helper setZRSimpleBarChartView:self.zrSimpleChartsView barValues:chart1Array xValues:titleArray];
}
繪制的主要代碼我都寫到了ZRChartsHelper
這個類中决侈,我們再看一下這個最基礎(chǔ)簡單的條形圖的實現(xiàn)代碼。
- (void)setZRSimpleBarChartView:(PYEchartsView *)chartView barValues:(NSArray *)barValues xValues:(NSArray *)xvals{
//初始化一個Option喧务,對其屬性進行設(shè)置赖歌,來達到我們想要的效果
PYOption *option = [PYOption initPYOptionWithBlock:^(PYOption *option) {
option.tooltipEqual([PYTooltip initPYTooltipWithBlock:^(PYTooltip *tooltip) {
tooltip.triggerEqual(PYTooltipTriggerAxis)
.axisPointerEqual([PYAxisPointer initPYAxisPointerWithBlock:^(PYAxisPointer *axisPoint) {
axisPoint.typeEqual(PYAxisPointerTypeShadow);
}]);
}])
//這個屬性是對圖表下方的文字控件進行設(shè)置
.legendEqual([PYLegend initPYLegendWithBlock:^(PYLegend *legend) {
//文字的內(nèi)容
legend.dataEqual(@[@"新增事件"]);
//文字控件的縱坐標
legend.yEqual(@300);
}])
//這個屬性是對整個圖表的位置進行設(shè)置
.gridEqual([PYGrid initPYGridWithBlock:^(PYGrid *grid) {
//第一個40為X軸距離左邊的距離,第二個x2為X軸末端距離view右面邊界的距離
grid.xEqual(@40).x2Equal(@50);
//圖表距離頂部的距離
grid.yEqual(@10);
//圖表的高度設(shè)置
grid.heightEqual(@250);
}])
//這個屬性是設(shè)置圖表可左右滑動功茴,很多情況下可能X軸要展示很多數(shù)據(jù)庐冯,因此會產(chǎn)生堆積,加了這個便可以左右滑動條形圖來查看數(shù)據(jù)
.dataZoomEqual([PYDataZoom initPYDataZoomWithBlock:^(PYDataZoom *dataZoom) {
dataZoom.yEqual(@335);
dataZoom.heightEqual(@10);
//設(shè)置為顯示滾動欄
dataZoom.showEqual(YES)
//下面這兩個屬性是設(shè)置界面一開始展示那一部分的內(nèi)容坎穿,這里為圖表30%~70%間的內(nèi)容
.startEqual(@30)
.endEqual(@70);
}])
//設(shè)置工具欄展父,這個我沒有讓它進行顯示,因此show這個屬性我設(shè)置為隱藏赁酝,大家可以去看看有很多功能
.toolboxEqual([PYToolbox initPYToolboxWithBlock:^(PYToolbox *toolbox) {
//設(shè)置為隱藏
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)
//設(shè)置X軸title犯祠,有多少個就在數(shù)組中寫入多少個
.addXAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
axis.typeEqual(PYAxisTypeCategory)
//這里在我封裝過后本應(yīng)寫為xvals,方便大家理解我直接填寫了數(shù)組進去
.addDataArr(@[@"人員1",@"人員2",@"人員3",@"人員4",@"人員5",@"人員6",@"人員7",@"人員8",@"人員9",@"人員10",@"人員11"]);
}])
//設(shè)置Y軸title酌呆,一般默認是數(shù)字
.addYAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
//Y軸顯示的值類型衡载,這里為直接顯示數(shù)據(jù)值
axis.typeEqual(PYAxisTypeValue);
//Y軸的位置,這里是在左邊
axis.positionEqual(PYPositionLeft);
}])
//這里設(shè)置柱子的屬性
.addSeries([PYCartesianSeries initPYCartesianSeriesWithBlock:^(PYCartesianSeries *series) {
//這個值可以隨意起名稱隙袁,他不會顯示在界面中痰娱,但是這個值很重要,我會在接下來復雜的柱狀圖中說明
series.stackEqual(@"事件類型")
//柱子的名稱菩收,與上方我們設(shè)置過的表格下方的控件相對應(yīng)
.nameEqual(@"新增事件")
//類型梨睁,bar為柱狀圖,如果設(shè)置為line則顯示為折線
.typeEqual(PYSeriesTypeBar)
//設(shè)置柱子的樣式
.itemStyleEqual([PYItemStyle initPYItemStyleWithBlock:^(PYItemStyle *itemStyle) {
itemStyle.normalEqual([PYItemStyleProp initPYItemStylePropWithBlock:^(PYItemStyleProp *normal) {
normal.labelEqual([PYLabel initPYLabelWithBlock:^(PYLabel *label) {
//是否顯示柱子的數(shù)值娜饵,以及顯示的位置
label.showEqual(YES).positionEqual(@"inside");
}]);
}]);
}])
//設(shè)置柱子的數(shù)值坡贺,X軸有多少個單位,這個數(shù)組就要對應(yīng)有多少值,封裝后這里應(yīng)填寫barValues
.dataEqual(@[@"56",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89",@"36",@"89"]);
}]);
}];
//為負責渲染的view設(shè)置渲染option
[chartView setOption:option];
//加載圖表渲染
[chartView loadEcharts];
}
以上就是一個最簡單的條形圖的設(shè)置遍坟,其實還好不算復雜拳亿,但是產(chǎn)品經(jīng)理怎么會這么輕松的放過你。
接下來就給大家看一下我司產(chǎn)品經(jīng)理的需求實現(xiàn)圖愿伴。
怎么樣肺魁,這個的設(shè)置就有點復雜了,其實說復雜也沒有很復雜隔节,掌握規(guī)律
就好了鹅经,我們再來看一下這個的實現(xiàn)代碼。
//布局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];
//這個數(shù)組的結(jié)構(gòu)是數(shù)組中嵌套數(shù)組怎诫,大家可以使用別的數(shù)據(jù)格式來進行封裝
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軸的數(shù)據(jù)
NSArray *titleArray = @[@"人員1",@"人員2",@"人員3",@"人員4",@"人員5",@"人員6",@"人員7",@"人員8",@"人員9",@"人員10",@"人員11"];
//為內(nèi)容進行渲染
ZRChartsHelper *helper = [[ZRChartsHelper alloc] init];
[helper setZRStackBarChartView:self.zrchartsView barValues:chart1Array xValues:titleArray];
}
開始還是一樣的瘾晃,我們先布局,然后使用helper
來進行界面的渲染幻妓,再來看一下stackbar
的實現(xiàn)代碼酗捌。
- (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)
//設(shè)置X軸title,有多少個就在數(shù)組中寫入多少個
.addXAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
axis.typeEqual(PYAxisTypeCategory)
.addDataArr(xvals);
}])
//設(shè)置Y軸title涌哲,一般默認是數(shù)字
.addYAxis([PYAxis initPYAxisWithBlock:^(PYAxis *axis) {
axis.typeEqual(PYAxisTypeValue);
axis.positionEqual(PYPositionLeft);
}])
//這個地方設(shè)置X軸每個單位中有幾個柱狀圖,每個柱狀圖有幾層
//******這里的設(shè)置就是重點******//
//大家可以看到尚镰,下面進行了四項設(shè)置阀圾,他們的nameEqual這個屬性名稱都不一樣,但是stackEqual這個屬性的內(nèi)容都一樣狗唉,這樣就會實現(xiàn)我們所要的堆積效果
.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];
}
可以看到實現(xiàn)柱狀圖的效果的關(guān)鍵就是stackEqual
初烘,nameEqual
這兩個屬性,大家可以嘗試一下分俯,設(shè)置幾個不同的stackEqual
肾筐,柱狀圖就會呈現(xiàn)一個X軸
對應(yīng)多個柱子
的效果,具體的效果以及代碼我都寫在Demo中了缸剪,Demo會在文章最下面的地址中給大家下載吗铐。
其實以上兩個界面的實現(xiàn)ios-charts這個組件也可以輕松的做到,我覺得不太方便的地方就是杏节,這個組件面對混合圖表
的設(shè)置看起來有那么一絲絲不太友好唬渗,導致我回去鉆研了兩天ECharts
。
這種條形圖加折線圖
的混合顯示圖表還是花了我一點時間去看文檔
的奋渔,先去看了一下JS
代碼中如何設(shè)置雙Y軸
镊逝,再回到iOS
的項目中試試能不能找到設(shè)置雙Y軸
的JS同名
屬性,如何設(shè)置坐標的顯示格式
等等嫉鲸,所以推薦大家遇到自己不太知道的實現(xiàn)方式時撑蒜,去看看百度文檔
中的JS
代碼的設(shè)置,再去ECharts
中尋找同名屬性
去試試,我也是慢慢試出來的座菠。
關(guān)鍵實現(xiàn)代碼:
//Y軸的設(shè)置變成一個數(shù)組狸眼,裝入了兩個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))
//設(shè)置類型為Line(折線)
.typeEqual(PYSeriesTypeLine)
.dataEqual(lineValues)
.itemStyleEqual([PYItemStyle initPYItemStyleWithBlock:^(PYItemStyle *itemStyle) {
itemStyle.normalEqual([PYItemStyleProp initPYItemStylePropWithBlock:^(PYItemStyleProp *normal) {
normal.borderColorEqual([PYColor colorWithHexString:@"#fff"])
.borderWidthEqual(@2)
//設(shè)置折線上label顯示的內(nèi)容
.labelEqual([PYLabel initPYLabelWithBlock:^(PYLabel *label) {
// 折線內(nèi)容顯示位置
label.positionEqual(@"inside")
//顯示為百分比
.formatterEqual(@"{c}%")
//文字顏色
.textStyleEqual([PYTextStyle initPYTextStyleWithBlock:^(PYTextStyle *textStyle) {
textStyle.colorEqual([PYColor colorWithHexString:@"#fff"]);
}]);
}]);
}]);
}]);
}]]);
以上就是幾種柱狀圖
的實現(xiàn)方式,應(yīng)該夠大部分場景使用了辈灼,我的demo中還封裝了簡單的餅狀圖
和環(huán)狀圖
份企,有新的圖表效果封裝我會持續(xù)更新demo,大家對照我的博客和demo理解了之后完全可以針對自己的項目做更好的封裝巡莹,我這個為了趕工可能有點粗糙司志,如果有幫到你就幫我點個贊就好啦。
Demo地址:ZRChartsHelper