最近因项目需求,要使用柱状图,第三方的东西固然很好,但是我还是想要写一个自己的柱状图,因为更加符合我们项目的需求,日后维护起来也会方便许多。
写了一个自定义视图,可以自定义众多属性,关键是使用起来方便,最少四行就可以了。
废话不多说,上代码:
WQLChartView.h文件:
1 typedef NS_ENUM(NSInteger ,ChartViewType) { 2 ChartViewTypeColumn, 3 ChartViewTypePoint 4 }; 5 6 @interface WQLChartView : UIView 7 /** 8 * 图表的类型 9 */ 10 @property (nonatomic,assign) ChartViewType type; 11 /** 12 * 单列的宽度 13 */ 14 @property (nonatomic,assign) CGFloat singleRowWidth; 15 /** 16 * 每个柱子之间的间距 17 */ 18 @property (nonatomic,assign) CGFloat singleChartSpace; 19 /** 20 * x的值 数组 21 */ 22 @property (nonatomic,strong) NSArray *xValueArray; 23 /** 24 * y的值 数组 25 */ 26 @property (nonatomic,strong) NSArray *yValueArray; 27 /** 28 * x轴的右侧标题(比如:时间) 29 */ 30 @property (nonatomic,copy) NSString *xAxleTitle; 31 /** 32 * y轴的顶部标题 (比如:万件) 33 */ 34 @property (nonatomic,copy) NSString *yAxleTitle; 35 /** 36 * y轴上有几个点 37 */ 38 @property (nonatomic,assign) NSInteger yAxlePointNumber; 39 /** 40 * y轴的坐标点 数组 41 */ 42 @property (nonatomic,strong) NSArray *yPointArray; 43 /** 44 * 柱子的颜色 45 */ 46 @property (nonatomic,strong) UIColor *columnColor; 47 /** 48 * 是否显示线条 49 */ 50 @property (nonatomic,assign) BOOL showLine; 51 /** 52 * 连线的颜色 53 */ 54 @property (nonatomic,strong) UIColor *lineColor; 55 /** 56 * 连线的宽度(粗细) 57 */ 58 @property (nonatomic,assign) CGFloat lineWidth; 59 /** 60 * 点的颜色(只有是point类型才有效) 61 */ 62 @property (nonatomic,strong) UIColor *pointColor; 63 /** 64 * 点的宽度(只有point类型才有效) 65 */ 66 @property (nonatomic,assign) CGFloat pointWidth; 67 /** 68 * 连接是否使用平滑的曲线 69 */ 70 @property (nonatomic,assign) BOOL lineIsCurve; 71 /** 72 * 是否隐藏数值 默认不隐藏 73 */ 74 @property (nonatomic,assign) BOOL isHideNumber; 75 /** 76 * 数值的颜色 77 */ 78 @property (nonatomic,strong) UIColor *colorOfNumber; 79 /** 80 * 数值的字号 81 */ 82 @property (nonatomic,assign) NSInteger fontSizeOfNumber; 83 84 //在superView上展示视图 必须调用 参数配置完毕后调用 85 - (void)showChartInView:(UIView*)superView; 86 87 //更新视图 88 - (void)updateView;
WQLChartView.m文件 核心代码:
1 #pragma mark 添加柱状图 2 - (void)loadColumnShapeWithSingleWidth:(CGFloat)singleW withSpace:(CGFloat)singleSpace 3 { 4 5 CGFloat viewHeight = self.bounds.size.height; 6 //如果改动了 则把之前的显示部分移除掉 7 if (isChange) { 8 [self deletePointLayer]; 9 } 10 11 //如果之前 添加了 则把之前的删了 12 if (columnShapeLayerArray.count > 0) { 13 for (CAShapeLayer *layer in columnShapeLayerArray) { 14 [layer removeFromSuperlayer]; 15 } 16 } 17 18 //把之前添加的线 移除掉 19 if (lineArray.count > 0) { 20 for (CAShapeLayer *lineLayer in lineArray) { 21 [lineLayer removeFromSuperlayer]; 22 } 23 } 24 25 columnShapeLayerArray = [NSMutableArray array]; 26 lineArray = [NSMutableArray array]; 27 28 UIBezierPath *column = [UIBezierPath bezierPath]; 29 30 CGFloat xPosition = 0; 31 if (!topPointYArray) { 32 topPointYArray = [NSMutableArray array]; 33 }else{ 34 [topPointYArray removeAllObjects]; 35 } 36 37 for (int i = 0; i<xCount; i++) { 38 CAShapeLayer *columnLayer = [CAShapeLayer layer]; 39 //当前的y值 40 NSString *value = _yValueArray[i]; 41 CGFloat yValue = [value floatValue]; 42 //y值 占坐标最大值的比率 43 CGFloat rate = yValue/yMax; 44 //单个柱子的高度 45 CGFloat singleHeight = (viewHeight-titleWidth-yArrowHeight)*rate; 46 47 NSString *topPointY = [NSString stringWithFormat:@"%f",viewHeight-titleWidth-singleHeight]; 48 [topPointYArray addObject:topPointY]; 49 50 //x轴 为柱子左下侧的点 y为坐标轴 51 [column moveToPoint:CGPointMake(xPosition+singleSpace+titleWidth, viewHeight-titleWidth)]; 52 //柱子的左侧垂直线 53 [column addLineToPoint:CGPointMake(xPosition+singleSpace+titleWidth, viewHeight-titleWidth-singleHeight)]; 54 //柱子的顶部水平线 55 [column addLineToPoint:CGPointMake(xPosition+singleSpace+titleWidth+singleW, viewHeight-titleWidth-singleHeight)]; 56 //柱子的右侧垂直线 57 [column addLineToPoint:CGPointMake(xPosition+singleSpace+titleWidth+singleW, viewHeight-titleWidth)]; 58 59 if (self.showLine) { 60 61 UIBezierPath *line = [UIBezierPath bezierPath]; 62 63 CAShapeLayer *lineLayer = [CAShapeLayer layer]; 64 65 if (i>0) { 66 //上一个点的y值 67 NSString *lastValue = _yValueArray[i-1]; 68 CGFloat lastYValue = [lastValue floatValue]; 69 CGFloat lastRate = lastYValue/yMax; 70 //取到上一个点的高度 71 CGFloat lastSingleHeight = (viewHeight-titleWidth-yArrowHeight)*lastRate; 72 73 //移到柱子的顶部中点 74 [line moveToPoint:CGPointMake(xPosition+singleSpace+titleWidth+singleW/2, viewHeight-titleWidth-singleHeight)]; 75 //向上一个点 添加连线 76 if (self.lineIsCurve) { 77 //曲线连接 78 //上一个点的x坐标 79 CGFloat lastPointX = xPosition+singleSpace+titleWidth+singleW/2-(singleW+singleSpace); 80 //上一个点的y坐标 81 CGFloat lastPointY = viewHeight - titleWidth-lastSingleHeight; 82 //该柱子顶部中点的x坐标 83 CGFloat pointX = xPosition+singleSpace+titleWidth+singleW/2; 84 //该柱子顶部中点的y坐标 85 CGFloat pointY = viewHeight-titleWidth-singleHeight; 86 //添加曲线 两个控制点 x为两个点的中间点 y为首末点的y坐标 为了实现平滑连接 87 [line addCurveToPoint:CGPointMake(lastPointX, lastPointY) controlPoint1:CGPointMake((pointX+lastPointX)/2, pointY) controlPoint2:CGPointMake((pointX+lastPointX)/2, lastPointY)]; 88 }else{ 89 //直线连接 90 [line addLineToPoint:CGPointMake(xPosition+singleSpace+titleWidth+singleW/2-(singleW+singleSpace), viewHeight-titleWidth-lastSingleHeight)]; 91 } 92 93 } 94 lineLayer.path = line.CGPath; 95 //线条宽度 96 lineLayer.lineWidth = self.lineWidth>0?self.lineWidth:2; 97 if (!self.lineColor) { 98 self.lineColor = [UIColor redColor]; 99 } 100 //线条颜色 101 lineLayer.strokeColor = self.lineColor.CGColor; 102 lineLayer.fillColor = [UIColor clearColor].CGColor; 103 [lineArray addObject:lineLayer]; 104 105 } 106 107 columnLayer.path = column.CGPath; 108 if (!self.columnColor) { 109 self.columnColor = [UIColor orangeColor]; 110 } 111 112 columnLayer.fillColor = CGColorCreateCopyWithAlpha(self.columnColor.CGColor, 1.0); 113 columnLayer.strokeStart = 0; 114 columnLayer.strokeEnd = 1.0; 115 [columnShapeLayerArray addObject:columnLayer]; 116 117 [self.layer addSublayer:columnLayer]; 118 119 for (CAShapeLayer *lineLayer in lineArray) { 120 [self.layer addSublayer:lineLayer]; 121 } 122 123 xPosition += (singleW+singleSpace); 124 125 } 126 } 127 #pragma mark 添加 点视图 128 - (void)loadPointInChartView 129 { 130 if (isChange) { 131 [self deleteColumnLayer]; 132 } 133 134 UIBezierPath *circle = [UIBezierPath bezierPath]; 135 136 CGFloat viewHeight = self.bounds.size.height; 137 138 //如果之前 添加了 则把之前的删了 139 if (pointShapeLayerArray.count > 0) { 140 for (CAShapeLayer *layer in pointShapeLayerArray) { 141 [layer removeFromSuperlayer]; 142 } 143 } 144 145 //把之前添加的线条删除了 146 if (lineArray.count > 0) { 147 for (CAShapeLayer *lineLayer in lineArray) { 148 [lineLayer removeFromSuperlayer]; 149 } 150 } 151 152 pointShapeLayerArray = [NSMutableArray array]; 153 lineArray = [NSMutableArray array]; 154 155 if (!topPointYArray) { 156 topPointYArray = [NSMutableArray array]; 157 }else{ 158 [topPointYArray removeAllObjects]; 159 } 160 //顶部中点连线 161 NSInteger count = topCenterPointXArray.count; 162 if (count >0) { 163 164 for (int i = 0; i<topCenterPointXArray.count; i++) { 165 166 CAShapeLayer *circleLayer = [CAShapeLayer layer]; 167 168 NSString *topPoint = topCenterPointXArray[i]; 169 CGFloat pointX = [topPoint floatValue]; 170 //y值 171 CGFloat yValues = [self.yValueArray[i] floatValue]; 172 //y坐标又是不一样了 y值越大,柱子越高,坐标其实是越小 173 CGFloat pointY = (1-(yValues/yMax))*(viewHeight-yArrowHeight-titleWidth)+yArrowHeight; 174 175 [topPointYArray addObject:[NSString stringWithFormat:@"%f",pointY]]; 176 177 [circle moveToPoint:CGPointMake(pointX, pointY)]; 178 179 CGFloat radius = self.pointWidth >0 ?self.pointWidth:5.0; 180 //标示点 为圆 181 [circle addArcWithCenter:CGPointMake(pointX, pointY) radius:radius startAngle:0 endAngle:M_PI*2 clockwise:YES]; 182 circleLayer.path = circle.CGPath; 183 if (!self.pointColor) { 184 self.pointColor = [UIColor orangeColor]; 185 } 186 //圆点的填充色 187 circleLayer.fillColor = CGColorCreateCopyWithAlpha(self.pointColor.CGColor, 1.0); 188 circleLayer.strokeStart = 0; 189 circleLayer.strokeEnd = 1.0; 190 circleLayer.lineWidth = 1; 191 [pointShapeLayerArray addObject:circleLayer]; 192 193 if (self.showLine) { 194 195 UIBezierPath *line = [UIBezierPath bezierPath]; 196 197 CAShapeLayer *lineLayer = [CAShapeLayer layer]; 198 199 if (i>0) { 200 NSString *lastXValue = topCenterPointXArray[i-1]; 201 CGFloat lastPointX = [lastXValue floatValue]; 202 203 NSString *lastValue = _yValueArray[i-1]; 204 CGFloat lastYValue = [lastValue floatValue]; 205 //y坐标又是不一样了 y值越大,柱子越高,坐标其实是越小 206 CGFloat lastPointY = (1-(lastYValue/yMax))*(viewHeight-yArrowHeight-titleWidth)+yArrowHeight; 207 208 //移到中点 209 [line moveToPoint:CGPointMake(pointX,pointY)]; 210 211 if (self.lineIsCurve) { 212 //曲线连接 两个控制点 x为两个点的中间点 y为首末点的y坐标 为了实现平滑连接 213 [line addCurveToPoint:CGPointMake(lastPointX, lastPointY) controlPoint1:CGPointMake((pointX+lastPointX)/2, pointY) controlPoint2:CGPointMake((pointX+lastPointX)/2, lastPointY)]; 214 }else{ 215 //向上一个点 添加连线 216 [line addLineToPoint:CGPointMake(lastPointX,lastPointY)]; 217 } 218 219 } 220 lineLayer.path = line.CGPath; 221 lineLayer.lineWidth = self.lineWidth>0?self.lineWidth:2; 222 if (!self.lineColor) { 223 self.lineColor = [UIColor blackColor]; 224 } 225 lineLayer.strokeColor = self.lineColor.CGColor; 226 lineLayer.fillColor = [UIColor clearColor].CGColor; 227 [lineArray addObject:lineLayer]; 228 229 } 230 231 } 232 } 233 234 for (CAShapeLayer *layer in pointShapeLayerArray) { 235 [self.layer addSublayer:layer]; 236 } 237 238 for (CAShapeLayer *lineLayer in lineArray) { 239 [self.layer addSublayer:lineLayer]; 240 } 241 242 }
总计八百多行,我就不一一贴出了。上述两个方法是核心的方法:
添加柱状图时,要注意坐标系的转换,因为我们布局的时候,坐标原点为视图的左上角,我们需要展示的柱状图的原点则是视图的左下角。
我们先获取到每一个y的值,然后计算出了每一个值占最大值的比率,然后用这个比率乘以y轴的总高度,则为对应的柱状图高度。
我们获取到每一个x值之后,要进行计算,比较用户设置的柱子的宽度*柱子的个数 与 初始设定的视图的宽度的大小,如果前者较大,则需要微调,以展示全部的柱子,后者较大,则不做处理。
对于线条的处理,直线图无需多余的处理,对于曲线图,使用的是有两个控制点的贝塞尔曲线,这两个控制点:
点A的x为始末点的中点的x,A的y为起点的y值。B的x为始末点的中点的x,y为末点的y值。这样就可以实现曲线平滑连接始末点了。
接下来看一下该怎么使用:
1 - (void)loadChartView 2 { 3 chartView = [[WQLChartView alloc]initWithFrame:CGRectMake(10, 100,chartWidth, chartHeight)]; 4 chartView.singleRowWidth = 50;//可注释掉 5 chartView.columnColor = [UIColor lightGrayColor];//可注释掉 6 chartView.pointColor = [UIColor orangeColor];//可注释掉 7 chartView.xAxleTitle = @"日";//可注释掉 8 chartView.yAxleTitle = @"件";//可注释掉 9 chartView.type = ChartViewTypeColumn;//可注释掉 10 chartView.showLine = YES;//可注释掉 11 chartView.lineColor = [UIColor blueColor];//可注释掉 12 chartView.lineIsCurve = YES;//可注释掉 13 chartView.colorOfNumber = [UIColor redColor];//可注释掉 14 chartView.yPointArray = @[@"20",@"40",@"60",@"80",@"100"];//可注释掉 15 chartView.xValueArray = xValuesArray; 16 chartView.yValueArray = yValuesArray; 17 [chartView showChartInView:self.view]; 18 19 }
最简单的就是全部非必要属性使用默认的,仅需4行即可加载柱状图。
回到要点上,功能才是王道:
添加柱子:
切换类型:
由头文件的属性可知,以下属性都可以修改:
图表的类型:分为柱状的和点状的
单列的宽度:柱状图时有效,默认25
每个柱子之间的间距:柱状图时有效
x的值数组:必须赋值的属性,为X轴上的点
y的值数组:必须赋值的属性,为对应的x的y值
x轴的右侧标题:可设置属性,为x轴的单位
y轴的顶部标题:可设置属性,为y轴的单位
y轴上的点的个数:可设置属性,默认为5个点,即y轴被分为5等份
y轴的坐标点数组:y轴上的刻度点
柱子的颜色:可设置属性,默认为橘黄色
是否显示线条:可设置属性,默认不显示线条
连线的颜色:可设置属性,默认为红色
连线的宽度:可设置属性,默认为2
点的颜色:点状图时有效,默认橙色
点的宽度:点状图时有效,标示的点的半径
是否使用平滑的曲线:连线的形状,默认使用直线连接
是否隐藏数值:不显示数字,默认为NO,不隐藏
数值的颜色:显示的数值的文本颜色,默认为黑色
数值的字号:数值的文本字号,默认为14号
需要额外提一点的是:用户的柱子宽度一定,当数量比较多时,原来的给定的尺寸不足以显示的时候,我将柱子的宽度缩小了以实现刚好填充的效果。
如有不足之处还请各位园友指导~一起学习一起进步!
完整的代码在这里: ChartView 1