zoukankan      html  css  js  c++  java
  • iOS 柱状图的定制

    最近因项目需求,要使用柱状图,第三方的东西固然很好,但是我还是想要写一个自己的柱状图,因为更加符合我们项目的需求,日后维护起来也会方便许多。

    写了一个自定义视图,可以自定义众多属性,关键是使用起来方便,最少四行就可以了。

    废话不多说,上代码:

    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                            

     

  • 相关阅读:
    HDU 3951 (博弈) Coin Game
    HDU 3863 (博弈) No Gambling
    HDU 3544 (不平等博弈) Alice's Game
    POJ 3225 (线段树 区间更新) Help with Intervals
    POJ 2528 (线段树 离散化) Mayor's posters
    POJ 3468 (线段树 区间增减) A Simple Problem with Integers
    HDU 1698 (线段树 区间更新) Just a Hook
    POJ (线段树) Who Gets the Most Candies?
    POJ 2828 (线段树 单点更新) Buy Tickets
    HDU 2795 (线段树 单点更新) Billboard
  • 原文地址:https://www.cnblogs.com/520myp1314/p/5311668.html
Copyright © 2011-2022 走看看