iOS程序猿iOSiOS开发那些事

CoreGraphics绘制图表教程(一)

2018-12-05  本文已影响18人  ChinaChong

参考自《Cocoa-Charts》

本篇介绍如何使用CoreGraphics绘制一个坐标系

为了学习如何绘制各种图表,特意去网上找了很多资料,看到了《Cocoa-Charts》这个开源框架,就学习了一下,在这里做一下分解后的记录。

原框架中画了很多类型的图表,包括折线图、柱状图、圆饼图、环形图以及区域填充图等等。我将在本篇开始逐个介绍这些图表是如何从零开始,到完全绘制成功。还有很多其它类型的图表都可以基于这几个基本类型去扩展。

CoreGraphics绘图三板斧

先大体说说CoreGraphics绘图的一些常用的方法,先有个大致了解。后面绘制各类图表基本上就是围绕这几个基本思想去做。

1.获取绘图上下文

CGContextRef context = UIGraphicsGetCurrentContext();

无论你想使用CoreGraphics做什么,你都得先获取到绘图上下文,我们就是依靠它来进行各种绘图操作。

2.画一条线

在绘制区域画一条线,你最少需要用到五个方法。

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetLineWidth(context, 1);

CGContextMoveToPoint(context, 10, 20);

CGContextAddLineToPoint(context, 100, 100);

CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);

CGContextStrokePath(context);

3.画一个矩形

CGContextRef context = UIGraphicsGetCurrentContext();
// 设置边框线宽
CGContextSetLineWidth(context, 2);
// 添加一个矩形路径
CGContextAddRect(context, CGRectMake(0, 0, 50, 50));
// 设置画笔颜色
CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
// 绘制路径
CGContextStrokePath(context);

4.画一个圆弧或者圆

CGContextRef context = UIGraphicsGetCurrentContext();
// 给定圆周率π
CGFloat pi = 3.141592653f;
// 添加一个圆
CGContextAddArc(context, 100, 100, 50, 0, pi/2, 0);
// 设置画笔颜色
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// 绘制圆弧
CGContextStrokePath(context);
一段圆弧

解释一下 CGContextAddArc函数的七个参数:

另外我总结了一下各个角度的位置,如图:

我们示例中起始弧度是0度,结束弧度是π/2,顺时针画就是效果图中的1/4圆。

但是细心地同学可能已经看出问题来了,我在 CGContextAddArc函数中设置的第七个参数是 0,对!0代表逆时针。那为什么逆时针会画出1/4圆,而不是3/4圆呢?

我去官方文档找了一下介绍《CGContextAddArc》

The clockwise parameter determines the direction in which the arc is created; the actual direction of the final path is dependent on the current transformation matrix of the graphics context. In a flipped coordinate system (the default for UIView drawing methods in iOS), specifying a clockwise arc results in a counterclockwise arc after the transformation is applied.

翻译:

顺时针参数确定创建弧的方向;最终路径的实际方向取决于图形上下文的当前转换矩阵。在翻转的坐标系中(iOS中UIView绘制方法的默认值),指定顺时针的圆弧会在应用转换后产生逆时针的圆弧。

看明白了吧,默认的就是翻转的,所以你自己没有翻转坐标系的情况下,就理解为 1 是逆时针,0 是顺时针就可以了。

4.1再来画一个,基于上面的例子画一个 1/4圆的扇形

与上面的不同点就是我们要给出起点位置 CGContextMoveToPoint,也就是圆弧的圆心。另外我们要把起点和终点闭合 CGContextClosePath,这样的封闭区域才能画出一个扇形

CGContextRef context = UIGraphicsGetCurrentContext();
// 给定圆周率π
CGFloat pi = 3.141592653f;
// 起始点移动到圆心
CGContextMoveToPoint(context, 100, 100);
// 添加一个圆
CGContextAddArc(context, 100, 100, 50, 0, pi/2, 0);
// 设置画笔颜色
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// 闭合路径,让起点和终点连接起来
CGContextClosePath(context);
// 绘制圆弧
CGContextStrokePath(context);

效果图:


1/4圆的扇形

5.填充指定区域

就比如我们填充刚刚画的那个 1/4圆的扇形

CGContextRef context = UIGraphicsGetCurrentContext();
// 给定圆周率π
CGFloat pi = 3.141592653f;
// 起始点移动到圆心
CGContextMoveToPoint(context, 100, 100);
// 添加一个圆
CGContextAddArc(context, 100, 100, 50, 0, pi/2, 0);
// 设置填充颜色
CGContextSetFillColorWithColor(context, [UIColor orangeColor].CGColor);
// 填充
CGContextFillPath(context);

效果图:


填充扇形

注意:我这里并没有闭合路径,但是填充依然会成功。因为填充行为会自动为你将起点和终点连接起来(即闭合路径)。但是 StrokePath 画笔画线则不然,它不会自动闭合路径。

我现在给出画1/4圆扇形时错误的例子:

❌错误1:不给出起点也不调用闭合函数就如图 《一段圆弧》所示

❌错误2:画1/4圆扇形的时候,给出起点但不调用闭合函数

CGContextRef context = UIGraphicsGetCurrentContext();
// 给定圆周率π
CGFloat pi = 3.141592653f;
// 起始点移动到圆心
CGContextMoveToPoint(context, 100, 100);
// 添加一个圆
CGContextAddArc(context, 100, 100, 50, 0, pi/2, 0);
// 设置画笔颜色
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
// 不调用闭合函数
// 绘制圆弧
CGContextStrokePath(context);

效果图:


不调用闭合函数画线

❌错误3:填充 1/4 扇形的时候,没有把圆心设为起点

CGContextRef context = UIGraphicsGetCurrentContext();
// 给定圆周率π
CGFloat pi = 3.141592653f;
// 起点未移动到圆心
// 添加一个圆
CGContextAddArc(context, 100, 100, 50, 0, pi/2, 0);
// 设置填充颜色
CGContextSetFillColorWithColor(context, [UIColor orangeColor].CGColor);
// 填充
CGContextFillPath(context);

效果图:


错误原因:之所以只填充了一个“月牙”,还是因为自动闭合路径的缘故。因为没有把圆心设为起点,所以它闭合的是圆弧的起点和终点。就是这段弧: 一段圆弧

6.绘制文字

// 设置字体
UIFont *textFont= [UIFont systemFontOfSize:15];
// 设置属性
NSDictionary *attrs = @{NSFontAttributeName:textFont,
                        NSForegroundColorAttributeName:[UIColor blackColor]};
// 文字的绘制区域
CGRect textRect = CGRectMake(100, 100, 100, 100);
// 绘制文字
[@"Hello World!" drawInRect:textRect withAttributes:attrs];

效果图:



7. 其它要点



前面这些准备工作做完,就可以去画各种图表了,下面开始进入正题

绘制坐标系

万事开头难,绝大多数图表的绘制都是从坐标系开始的,首先要学会建立一个简单实用的坐标系,为之后绘制各类图表打下一个好基础。

之前介绍过,CoreText的坐标系与我们平时Coding时使用的UIKit坐标系Y轴是倒转的。在这里介绍的CoreGraphics坐标系就不用担心这个问题,我们在绘制过程中用的CoreGraphics坐标系与UIKit完全一致,即原点在屏幕左上角,X轴向右为正方向,Y轴向下为正方向。

绘制坐标系的最终效果图

坐标系的结构组成:

总共就上面这几点,这也是绘制一个坐标系的基本思路。

首先,绘制坐标系的外边框

与CoreText一样的是,所有的绘制操作都是从drawRect: 开始,而且同样要获取绘图上下文 CGContextRef

创建一个继承自 UIView 的类,取名 CoordinateSystem,用于绘制坐标系。在 CoordinateSystemdrawRect: 方法中Coding。

Love My Job

好,开始绘制外边框。获取绘图上下文 CGContextRef,并设置绘制区域的填充色,进行填充。

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
    CGContextFillRect(context, rect);
}

现在可以去 ViewController 加载一下 CoordinateSystem ,背景颜色设置为cyanColor,效果如下



上图就是填充后的样子,整个绘制区域被填充成 cyanColor

接下来,设置边框线宽-->设置起始点-->在绘制区域添加一个矩形路径-->设置画笔颜色-->绘制路径

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
    CGContextFillRect(context, rect);
    
    CGContextSetLineWidth(context, 2);
    
    CGContextMoveToPoint(context, 0.0f, 0.0f);
    CGContextAddRect(context, rect);
    
    CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
    CGContextStrokePath(context);
}

效果如下:



绘制X轴线

绘制的X轴线要注意,不能紧贴绘制区域底部,因为后面还要绘制X轴上的刻度,要留出一部分空间。下部间隙暂定 15 像素。

static CGFloat axisMarginBottom = 15;
- (void)drawRect:(CGRect)rect {
    // 获取绘图上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 设置背景填充色
    CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
    // 填充整个绘制区域
    CGContextFillRect(context, rect);
    
    // 设置边框线宽
    CGContextSetLineWidth(context, 2);
    // 设置起始点
    CGContextMoveToPoint(context, 0.0f, 0.0f);
    // 添加一个矩形路径
    CGContextAddRect(context, rect);
    // 设置画笔颜色
    CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
    // 绘制路径
    CGContextStrokePath(context);
    
    // 重新设置X轴线宽
    CGContextSetLineWidth(context, 1);
    // 设置绘制X轴的起始点
    CGContextMoveToPoint(context, 0.0f, rect.size.height - axisMarginBottom);
    // 添加绘制X轴线的路径
    CGContextAddLineToPoint(context, rect.size.width, rect.size.height - axisMarginBottom);
    // 设置画笔颜色
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    // 绘制路径
    CGContextStrokePath(context);
}

效果如下:



绘制Y轴线

绘制Y轴线要在左侧留出一部分空间,与X轴对称,暂定左侧间距 15 像素

static CGFloat axisMarginBottom = 15;
static CGFloat axisMarginLeft = 15;
- (void)drawRect:(CGRect)rect {
    // 获取绘图上下文
    CGContextRef context = UIGraphicsGetCurrentContext();
    // 设置背景填充色
    CGContextSetFillColorWithColor(context, self.backgroundColor.CGColor);
    // 填充整个绘制区域
    CGContextFillRect(context, rect);
    
    // 设置边框线宽
    CGContextSetLineWidth(context, 2);
    // 设置起始点
    CGContextMoveToPoint(context, 0.0f, 0.0f);
    // 添加一个矩形路径
    CGContextAddRect(context, rect);
    // 设置画笔颜色
    CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
    // 绘制路径
    CGContextStrokePath(context);
    
    // 重新设置X轴线宽
    CGContextSetLineWidth(context, 1);
    // 设置绘制X轴的起始点
    CGContextMoveToPoint(context, 0.0f, rect.size.height - axisMarginBottom);
    // 添加绘制X轴线的路径
    CGContextAddLineToPoint(context, rect.size.width, rect.size.height - axisMarginBottom);
    // 设置画笔颜色
    CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
    // 绘制路径
    CGContextStrokePath(context);
    
    // 绘制Y轴线
    CGContextMoveToPoint(context, axisMarginLeft, 0.0f);
    CGContextAddLineToPoint(context, axisMarginLeft, rect.size.height);
    
    CGContextStrokePath(context);
}

效果图:



绘制经线(纵向刻度线、虚线)

纵向的刻度线可以根据数据源的个数,也可以自定固定的个数,我这里是以数据源的个数为准,有多少组数据画多少条经线。

坐标系右侧留出一定空间,暂定右侧间距为 axisMarginRight 3 像素。

- (void)drawLongitudeLines:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 1);
    CGContextSetStrokeColorWithColor(context, [UIColor orangeColor].CGColor);
    
    if ([self.longitudeTitles count] <= 0) {
        return;
    }
    
    //设置线条为虚线
    CGFloat lengths[] = {3.0, 2.0};
    CGContextSetLineDash(context, 0.0, lengths, 2);
    
    CGFloat postOffset;
    CGFloat offset;
    
    postOffset = (rect.size.width - axisMarginLeft - axisMarginRight) / (self.longitudeTitles.count - 1);
    offset = axisMarginLeft;
    
    for (int i = 1; i < self.longitudeTitles.count ; i++) {
        CGContextMoveToPoint(context, offset + i * postOffset, 0);
        CGContextAddLineToPoint(context, offset + i * postOffset, rect.size.height - axisMarginBottom);
    }
    
    CGContextStrokePath(context);
    CGContextSetLineDash(context, 0, nil, 0);
}

思路:
postOffset 是经线之间的间距,比如要绘制8条经线,那么就把整个绘制区域平均分成7份,每一份就是一个 postOffset

offset 是绘制起始点的X轴坐标,也就是坐标系在左侧留出的空间 axisMarginLeft

这里说明一下设置虚线的参数 lengths,数组第一个参数 3.0 代表线段长度,第二个参数 2.0 代表线段间距。

CGFloat lengths[] = {3.0, 2.0};
CGContextSetLineDash(context, 0.0, lengths, 2);

效果图:


绘制X轴刻度

X轴刻度需要相应的数据源,所以 CoordinateSystem 类要能够接收X轴刻度(X轴标题)数据源,因此添加一个属性 NSArray *longitudeTitles 来接收。

单独写一个方法来绘制X轴刻度:

- (void)drawXAxisTitles:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 0.5f);
    
    if ([self.longitudeTitles count] <= 0) {
        return;
    }
    
    CGFloat postOffset;
    CGFloat offset;
    
    postOffset = (rect.size.width - axisMarginLeft - axisMarginRight) / (self.longitudeTitles.count - 1);
    offset = axisMarginLeft;
    
    for (int i = 0; i < [self.longitudeTitles count]; i++) {
        
        // 绘制线条
        NSString *valueStr = (NSString *) [self.longitudeTitles objectAtIndex:i];
        UIFont *textFont= [UIFont systemFontOfSize:12]; //设置字体
        NSMutableParagraphStyle *textStyle=[[NSMutableParagraphStyle alloc]init];//段落样式
        textStyle.lineBreakMode = NSLineBreakByWordWrapping;
        
        NSDictionary *attrs = @{NSFontAttributeName:textFont,
                                NSParagraphStyleAttributeName:textStyle,
                                NSForegroundColorAttributeName:[UIColor blackColor]};
        CGSize textSize = [valueStr boundingRectWithSize:CGSizeMake(100, 100)
                                                 options:NSStringDrawingUsesLineFragmentOrigin
                                              attributes:attrs
                                                 context:nil].size;
        
        // 调整X轴坐标位置
        // 第一个刻度的位置要绘制在Y轴线右侧
        if (i == 0) {
            CGRect textRect= CGRectMake(axisMarginLeft, rect.size.height - axisMarginBottom, textSize.width, textSize.height);
            textStyle.alignment=NSTextAlignmentLeft;
            // 绘制字体
            [valueStr drawInRect:textRect withAttributes:attrs];
            
        }
        // 最后一个刻度的位置要绘制在最后一条经线的左侧
        else if (i == self.longitudeTitles.count-1) {
            CGRect textRect= CGRectMake(rect.size.width - axisMarginRight - textSize.width, rect.size.height - axisMarginBottom, textSize.width, textSize.height);
            textStyle.alignment=NSTextAlignmentRight;
            // 绘制字体
            [valueStr drawInRect:textRect withAttributes:attrs];
        } else {
            CGRect textRect= CGRectMake(offset + (i-0.5) * postOffset, rect.size.height - axisMarginBottom, postOffset, textSize.height);
            textStyle.alignment=NSTextAlignmentCenter;
            // 绘制字体
            [valueStr drawInRect:textRect withAttributes:attrs];
        }
    }
}

思路:
把你要绘制在X轴上的刻度数据传进 longitudeTitles 数组中,逐个设置属性,计算 Size,确定每一个刻度的位置,最后使用字符串调用 drawInRect: withAttributes: 方法绘制字体。

效果图:



绘制纬线(横向刻度线、虚线)

与绘制经线一样,我们以数据源的个数为准,Y轴绘制纬线的数据源定为 NSArray *latitudeTitles

我在绘制纬线的时候在上部也同样留出了一定的空间,不让它顶格,好看一点,间距是 axisMarginTop 3 像素

- (void)drawLatitudeLines:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 1);
    CGContextSetStrokeColorWithColor(context, [UIColor orangeColor].CGColor);
    
    if ([self.latitudeTitles count] <= 0){
        return ;
    }
    //设置线条为虚线
    CGFloat lengths[] = {3.0, 3.0}; // 线宽和间距,长短相间可以画两条虚线,然后拼接在一起
    CGContextSetLineDash(context, 0.0, lengths, 2);
    
    CGFloat postOffset; // 纬线之间的间距
    postOffset = (rect.size.height - axisMarginBottom - axisMarginTop) * 1.0 / ([self.latitudeTitles count] - 1);
    
    CGFloat offset = rect.size.height - axisMarginBottom;
    
    for (int i = 1; i < [self.latitudeTitles count]; i++) {
        CGContextMoveToPoint(context, 0, offset - i * postOffset);
        CGContextAddLineToPoint(context, rect.size.width , offset - i * postOffset);
    }
    CGContextStrokePath(context);
    //还原线条
    CGContextSetLineDash(context, 0, nil, 0);
}

效果图:



绘制Y轴刻度

- (void)drawYAxisTitles:(CGRect)rect {
    if ([self.latitudeTitles count] <= 0) {
        return;
    }
    
    CGFloat postOffset;
    postOffset = (rect.size.height - axisMarginBottom - axisMarginTop) * 1.0 / ([self.latitudeTitles count] - 1);
    
    CGFloat offset = rect.size.height - axisMarginBottom;
    
    for (int i = 0; i < [self.latitudeTitles count]; i++) {
        // 左侧
        // 绘制线条
        NSString *valueStr = (NSString *) [self.latitudeTitles objectAtIndex:i];
        UIFont *textFont= [UIFont systemFontOfSize:12]; //设置字体
        NSMutableParagraphStyle *textStyle=[[NSMutableParagraphStyle alloc]init];//段落样式
        textStyle.lineBreakMode = NSLineBreakByWordWrapping;
        
        NSDictionary *attrs = @{NSFontAttributeName:textFont,
                                NSParagraphStyleAttributeName:textStyle,
                                NSForegroundColorAttributeName:[UIColor blackColor]};
        CGSize textSize = [valueStr boundingRectWithSize:CGSizeMake(100, 100)
                                                 options:NSStringDrawingUsesLineFragmentOrigin
                                              attributes:attrs
                                                 context:nil].size;
        /*
         显示左边
         */
        textStyle.alignment=NSTextAlignmentLeft;
        //调整Y轴坐标位置
        if (i == [self.latitudeTitles count] - 1) {
            CGRect textRect= CGRectMake(axisMarginLeft, offset - i * postOffset, textSize.width, textSize.height);
            //绘制字体
            [valueStr drawInRect:textRect withAttributes:attrs];
        } else {
            CGRect textRect= CGRectMake(axisMarginLeft, offset - i * postOffset - textSize.height - 1, textSize.width, textSize.height);
            //绘制字体
            [valueStr drawInRect:textRect withAttributes:attrs];
        }
    }
}

效果图:



最后,绘制十字交叉线

绘制十字交叉线是本篇最复杂的一个部分,其实也没有那么吓人。原理就是在用户点击或者平移的时候,显示出触摸点的XY轴信息即可。用户触摸点的XY坐标我们是可以获取到的,拿到了XY坐标就可以算出XY轴具体要显示什么信息。

用户点击屏幕时,我们可以通过 touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 方法中的 touches 获取触摸点的XY坐标。

用户平移时,我们可以通过 touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 中的 touches 获取触摸点的XY坐标。

下面是源代码:

/**
 绘制十字交叉线
 
 @param rect 绘制区域
 */
- (void)drawCrossLines:(CGRect)rect {
    
    //过滤非显示区域的点
    if (self.singleTouchPoint.x < axisMarginLeft ||
        self.singleTouchPoint.y < axisMarginTop ||
        self.singleTouchPoint.x > rect.size.width - axisMarginRight ||
        self.singleTouchPoint.y > rect.size.height - axisMarginBottom) {
        return;
    }
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 1.0f);
    
    //设置线条为虚线
    CGFloat lengths[] = {2.0, 2.0};
    CGContextSetLineDash(context, 0.0, lengths, 1);
    
    
    // 绘制纵向刻度文字
    NSString *valueStr = [self calcAxisXGraduate:rect];
    if (![valueStr isEqualToString:@""]) {
        CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
        CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
        
        //绘制纵线
        //还原半透明
        CGContextSetAlpha(context, 1);
        // 移动初始点
        CGContextMoveToPoint(context, self.singleTouchPoint.x, 0);
        // 添加line
        CGContextAddLineToPoint(context, self.singleTouchPoint.x, rect.size.height - axisMarginBottom);
        //绘制线条
        CGContextStrokePath(context);
        
        // 绘制字体
        UIFont *textFont= [UIFont systemFontOfSize:12]; //设置字体
        NSMutableParagraphStyle *textStyle=[[NSMutableParagraphStyle alloc]init];//段落样式
        textStyle.lineBreakMode = NSLineBreakByWordWrapping;
        textStyle.alignment=NSTextAlignmentCenter;
        
        NSDictionary *attrs = @{NSFontAttributeName:textFont,
                                NSParagraphStyleAttributeName:textStyle,
                                NSForegroundColorAttributeName:[UIColor whiteColor]};
        CGSize textSize = [valueStr boundingRectWithSize:CGSizeMake(100, 100)
                                                 options:NSStringDrawingUsesLineFragmentOrigin
                                              attributes:attrs
                                                 context:nil].size;
        CGRect boxRect = CGRectMake(self.singleTouchPoint.x - textSize.width / 2.0, 1, textSize.width, textSize.height);
        
        CGContextAddRect(context,boxRect);
        CGContextFillPath(context);
        
        [valueStr drawInRect:boxRect withAttributes:attrs];
    }
    
    // 绘制横向刻度文字
    NSString *valueStr2 = [self calcAxisYGraduate:rect];
    if (![valueStr2 isEqualToString:@""]) {
        CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
        CGContextSetFillColorWithColor(context, [UIColor redColor].CGColor);
        
        //绘制横线
        //还原半透明
        CGContextSetAlpha(context, 1);
        
        CGContextMoveToPoint(context, 0, self.singleTouchPoint.y);
        CGContextAddLineToPoint(context, rect.size.width, self.singleTouchPoint.y);
        
        //绘制线条
        CGContextStrokePath(context);
        
        // 绘制字体
        UIFont *textFont2= [UIFont systemFontOfSize:12]; //设置字体
        NSMutableParagraphStyle *textStyle2 = [[NSMutableParagraphStyle alloc] init];//段落样式
        textStyle2.lineBreakMode = NSLineBreakByWordWrapping;
        textStyle2.alignment=NSTextAlignmentLeft;
        
        NSDictionary *attrs2 = @{NSFontAttributeName:textFont2,
                                 NSParagraphStyleAttributeName:textStyle2,
                                 NSForegroundColorAttributeName:[UIColor whiteColor]};
        CGSize textSize2 = [valueStr2 boundingRectWithSize:CGSizeMake(100, 100)
                                                   options:NSStringDrawingUsesLineFragmentOrigin
                                                attributes:attrs2
                                                   context:nil].size;
        CGRect boxRect2 = CGRectMake(1, self.singleTouchPoint.y - textSize2.height / 2.0, textSize2.width, textSize2.height);
        
        CGContextAddRect(context,boxRect2);
        CGContextFillPath(context);
        
        [valueStr2 drawInRect:boxRect2 withAttributes:attrs2];
    }
    
    CGContextSetLineDash(context, 0, nil, 0);
}

// 获取十字交叉线的X轴刻度
- (NSString *)calcAxisXGraduate:(CGRect)rect {
    return [NSString stringWithFormat:@"%f", [self touchPointAxisXValue:rect]];
}

// 获取十字交叉线的Y轴刻度
- (NSString *)calcAxisYGraduate:(CGRect)rect {
    return [NSString stringWithFormat:@"%f", [self touchPointAxisYValue:rect]];
}

// 计算触摸点X坐标值占坐标系宽度比例
- (CGFloat)touchPointAxisXValue:(CGRect)rect {
    CGFloat length = rect.size.width - self.axisMarginLeft - self.axisMarginRight;
    CGFloat valueLength = self.singleTouchPoint.x - self.axisMarginLeft ;
    return valueLength / length;
}

// 计算触摸点Y坐标值占坐标系高度比例
- (CGFloat)touchPointAxisYValue:(CGRect)rect {
    CGFloat length = rect.size.height - self.axisMarginBottom - self.axisMarginTop;
    CGFloat valueLength = length - (self.singleTouchPoint.y - self.axisMarginTop);
    
    return valueLength / length;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    
    NSArray *allTouches = [touches allObjects];
    //处理点击事件
    if ([allTouches count] == 1) {
        //获取选中点
        self.singleTouchPoint = [[allTouches objectAtIndex:0] locationInView:self];
        //重绘
        [self setNeedsDisplay];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    
    NSArray *allTouches = [touches allObjects];
    //处理点击事件
    if ([allTouches count] == 1) {
        //获取选中点
        self.singleTouchPoint = [[allTouches objectAtIndex:0] locationInView:self];
        //重绘
        [self setNeedsDisplay];
    }
}

效果图:



Github示例源码

链接地址:CoreGraphicsDrawChart

上一篇下一篇

猜你喜欢

热点阅读