CoreGraphics繪制圖表教程(一)

參考自《Cocoa-Charts》

本篇介紹如何使用CoreGraphics繪制一個坐標系

為了學習如何繪制各種圖表,特意去網上找了很多資料,看到了《Cocoa-Charts》這個開源框架,就學習了一下,在這里做一下分解后的記錄。

原框架中畫了很多類型的圖表,包括折線圖、柱狀圖、圓餅圖、環形圖以及區域填充圖等等。我將在本篇開始逐個介紹這些圖表是如何從零開始,到完全繪制成功。還有很多其它類型的圖表都可以基于這幾個基本類型去擴展。

CoreGraphics繪圖三板斧

先大體說說CoreGraphics繪圖的一些常用的方法,先有個大致了解。后面繪制各類圖表基本上就是圍繞這幾個基本思想去做。

1.獲取繪圖上下文

CGContextRef context = UIGraphicsGetCurrentContext();

無論你想使用CoreGraphics做什么,你都得先獲取到繪圖上下文,我們就是依靠它來進行各種繪圖操作。

2.畫一條線

在繪制區域畫一條線,你最少需要用到五個方法。

  • 設置線寬 CGContextSetLineWidth(context, 1);
  • 設置線的起始點 CGContextMoveToPoint(context, 10, 20);
  • 設置線的終點,形成一條繪制路徑 CGContextAddLineToPoint(context, 100, 100);
  • 設置畫筆的顏色 CGContextSetStrokeColorWithColor(context, [UIColor blackColor].CGColor);
  • 使用畫筆畫線 CGContextStrokePath(context);
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函數的七個參數:

  • 第一個是繪圖上下文

  • 第二個是圓心的X坐標

  • 第三個是圓心的Y坐標

  • 第四個是圓的半徑

  • 第五個是圓弧的起始弧度

  • 第六個是圓弧的結束弧度

  • 第七個是你是否要指定按照順時針方向,1為順時針,0為逆時針

另外我總結了一下各個角度的位置,如圖:

我們示例中起始弧度是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);

效果圖:


錯誤原因:之所以只填充了一個“月牙”,還是因為自動閉合路徑的緣故。因為沒有把圓心設為起點,所以它閉合的是圓弧的起點和終點。就是這段?。?div id="ssm2444" class="image-package">
一段圓弧


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. 其它要點

  • CGContextAddLineToPoint添加完線段后,起始點也移動到新的端點處,再接 CGContextAddLineToPoint 會從新的端點處開始畫

  • CGContextFillPathCGContextStrokePath 會消耗掉當前context中的點和線(即路徑),再次調用需要重新設置起始點或路徑

  • CGContextFillPath 填充最多可以兩條線的路徑實現閉合,三條線的路徑就失靈了。這條是親測的?。?!

  • CGContextClosePath 在路徑的終點和起點之間追加一條線。如果你打算填充一段路徑,那么就不需要使用該命令,因為該命令會被自動調用。

  • 兩個不相干的封閉路徑都可以被一次性正常填充,比如兩個矩形




前面這些準備工作做完,就可以去畫各種圖表了,下面開始進入正題

繪制坐標系

萬事開頭難,絕大多數圖表的繪制都是從坐標系開始的,首先要學會建立一個簡單實用的坐標系,為之后繪制各類圖表打下一個好基礎。

之前介紹過,CoreText的坐標系與我們平時Coding時使用的UIKit坐標系Y軸是倒轉的。在這里介紹的CoreGraphics坐標系就不用擔心這個問題,我們在繪制過程中用的CoreGraphics坐標系與UIKit完全一致,即原點在屏幕左上角,X軸向右為正方向,Y軸向下為正方向。

繪制坐標系的最終效果圖

坐標系的結構組成:

  • 外邊框

  • X軸線

  • Y軸線

  • 緯線(橫向刻度線,黃色虛線)

  • 經線(縱向刻度線,黃色虛線)

  • X軸刻度(X軸上的標題)

  • Y軸刻度(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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容

  • 【Android 自定義View之繪圖】 基礎圖形的繪制 一、Paint與Canvas 繪圖需要兩個工具,筆和紙。...
    Rtia閱讀 11,713評論 5 34
  • 寫在前面 ggplot2 是一個功能強大且靈活的R包 ,由Hadley Wickham 編寫,其用于生成優雅的圖...
    Boer223閱讀 28,208評論 0 67
  • 1 夏夜的凌晨,悶熱逼仄的二樓,窗外樹葉沒有一點動靜,風不知躲到哪兒去了。 我的夢在汗水里起起伏伏,拌著老公的呼嚕...
    心字羅衣閱讀 1,148評論 47 42
  • HTTP狀態碼(HTTP Status Code)是用以表示網頁HTTP響應狀態的3位數字代碼。
    anyurchao閱讀 242評論 0 0