5

I'am trying to learn and understand CoreGraphics. What i'am trying to do is making a pie chart.

The pie chart is working fine and looks great, but i'am having trouble with clipping an inner circle.

This the code for each slide in the pie:

CGPoint center = CGPointMake((self.bounds.size.width/2) + self.centerOffset, (self.bounds.size.height/2) - self.centerOffset);
CGFloat radius = MIN(center.x, center.y) - 25;
radius *= self.pieScale;

CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, center.x, center.y);

CGPoint p1 = CGPointMake(center.x + radius * cosf(self.startAngle), center.y + radius * sinf(self.startAngle));
CGContextAddLineToPoint(ctx, p1.x, p1.y);

int clockwise = self.startAngle > self.endAngle;
CGContextAddArc(ctx, center.x, center.y, radius, self.startAngle, self.endAngle, clockwise);
CGContextClosePath(ctx);

CGContextMoveToPoint(ctx, center.x, center.y);
CGContextAddArc(ctx, center.x, center.y, radius*0.5, self.startAngle, self.endAngle, clockwise);

CGContextSetFillColorWithColor(ctx, self.fillColor.CGColor);
CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
CGContextSetLineWidth(ctx, self.strokeWidth);

self.pathRef = CGContextCopyPath(ctx);
CGContextDrawPath(ctx, kCGPathFillStroke);

My current pie looks like this:

http://cl.ly/image/1v1D3l3O0u3T

I managed to add an inner circle by drawing a new path with a smaller radius.

I try to clip the second path with CGContextClip(ctx); but leaves me only with the inner circle like this:

http://cl.ly/image/2G402n3G3J2G

It kind of makes sense to me why this is happening but i can't figure what else i should be doing.

Edit:

Code now looks like:

CGPoint center = CGPointMake((self.bounds.size.width/2) + self.centerOffset, (self.bounds.size.height/2) - self.centerOffset);
CGFloat radius = MIN(center.x, center.y) - 25;
radius *= self.pieScale;
CGPoint p1 = CGPointMake(center.x + radius * cosf(self.startAngle), center.y + radius * sinf(self.startAngle));
int clockwise = self.startAngle > self.endAngle;

CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, center.x, center.y);

CGContextAddLineToPoint(ctx, p1.x, p1.y);
CGContextAddArc(ctx, center.x, center.y, radius, self.startAngle, self.endAngle, clockwise);
CGContextClosePath(ctx);

CGContextMoveToPoint(ctx, center.x, center.y);
CGContextAddArc(ctx, center.x, center.y, radius*0.5, self.startAngle, self.endAngle, !clockwise);


CGContextSetFillColorWithColor(ctx, self.fillColor.CGColor);
CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
CGContextSetLineWidth(ctx, self.strokeWidth);

self.pathRef = CGContextCopyPath(ctx);
CGContextDrawPath(ctx, kCGPathFillStroke);

Looks like:

enter image description here

All drawing code:

My class is a subclass of CALayer. This code is drawing a single slice in the pie.

-(void)drawInContext:(CGContextRef)ctx
{
    CGPoint center = CGPointMake((self.bounds.size.width/2) + self.centerOffset, (self.bounds.size.height/2) - self.centerOffset);
    CGFloat radius = MIN(center.x, center.y) - 25;
    radius *= self.pieScale;
    int clockwise = self.startAngle > self.endAngle;

    /* Clipping should be done first so the next path(s) are not creating the clipping mask */
    CGContextMoveToPoint(ctx, center.x, center.y);
    CGContextAddArc(ctx, center.x, center.y, radius*0.5, self.startAngle, self.endAngle, !clockwise);
    //CGContextClipPath(ctx);
    CGContextClip(ctx);

    /* Now, start drawing your graph and filling things in... */
    CGContextBeginPath(ctx);
    CGContextMoveToPoint(ctx, center.x, center.y);

    CGPoint p1 = CGPointMake(center.x + radius * cosf(self.startAngle), center.y + radius * sinf(self.startAngle));

    CGContextAddLineToPoint(ctx, p1.x, p1.y);
    CGContextAddArc(ctx, center.x, center.y, radius, self.startAngle, self.endAngle, clockwise);
    CGContextClosePath(ctx);


    CGContextSetFillColorWithColor(ctx, self.fillColor.CGColor);
    CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
    CGContextSetLineWidth(ctx, self.strokeWidth);

    self.pathRef = CGContextCopyPath(ctx);
    CGContextDrawPath(ctx, kCGPathFillStroke);

    // LABELS
    UIGraphicsPushContext(ctx);
    CGContextSetFillColorWithColor(ctx, self.labelColor.CGColor);

    CGFloat distance = [self angleDistance:(self.startAngle * 180/M_PI) angle2:(self.endAngle * 180/M_PI)];
    CGFloat arcDistanceAngle = distance * M_PI/180;
    CGFloat arcCenterAngle = self.startAngle + arcDistanceAngle/2;

    CGPoint labelPoint = CGPointMake(center.x + radius * cosf(arcCenterAngle), center.y + radius * sinf(arcCenterAngle));


    /*
     Basic drawing of lines to labels.. Disabled for now..
    CGContextBeginPath(ctx);
    CGContextMoveToPoint(ctx, labelPoint.x, labelPoint.y);
    */

    if(labelPoint.x <= center.x)
        labelPoint.x -= 50;
    else
        labelPoint.x += 5;

    if(labelPoint.y <= center.y)
        labelPoint.y -= 25;



    /* 
     Basic drawing of lines to labels.. Disabled for now..
    CGContextAddLineToPoint(ctx, labelPoint.x, labelPoint.y);
    CGContextClosePath(ctx);

    CGContextSetFillColorWithColor(ctx, self.fillColor.CGColor);
    CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
    CGContextSetLineWidth(ctx, self.strokeWidth);
    CGContextDrawPath(ctx, kCGPathFillStroke);
    */

    [self.labelString drawAtPoint:labelPoint forWidth:50.0f withFont:[UIFont systemFontOfSize:18] lineBreakMode:NSLineBreakByClipping];
    UIGraphicsPopContext();

}
4

2 回答 2

7

如果您不想拥有饼图的中心(即制作所谓的甜甜圈车),那么我建议您创建单独的甜甜圈段的形状,而不是制作饼片并掩盖中心。

可能会想到的第一件事是用两条直线和两条半径不同但中心坐标相同的圆弧共同创建该段。幸运的是,在 Core Graphics 中有更简单的方法来制作这个形状。这种形状实际上只是一个角度和另一个角度之间的单一弧线,但更厚。我已经在这个答案中解释了这一切(对问题“从圆圈或甜甜圈绘制线段”),但这里是对甜甜圈段形状的简短解释。

第 1 步:创建圆弧

创建将位于甜甜圈段中心的圆弧(下图中的橙色线)。半径将为。(rmax + rmin) / 2

在此处输入图像描述

CGMutablePathRef arc = CGPathCreateMutable();
CGPathMoveToPoint(arc, NULL,
                  startPoint.x, startPoint.y);
CGPathAddArc(arc, NULL,
             centerPoint.x, centerPoint.y,
             radius,
             startAngle,
             endAngle,
             YES);

第 2 步:画出弧线

通过抚摸弧线创建最终的甜甜圈段形状。这个功能对你来说可能看起来很神奇,但这就是在 Core Graphics 中找到隐藏宝藏的感觉。它将以特定的笔划宽度笔划路径。“线帽”和“线连接”控制形状的开始和结束的外观以及路径组件之间的连接外观(此形状中只有一个组件)。

在此处输入图像描述

CGFloat lineWidth = 10.0; // any radius you want
CGPathRef donutSegment =
    CGPathCreateCopyByStrokingPath(arc, NULL,
                                   lineWidth,
                                   kCGLineCapButt,
                                   kCGLineJoinMiter, // the default
                                   10); // 10 is default miter limit

第3步:没有第3步(嗯,有填充+描边)

就像你对饼形做的那样填充这个形状。(在图 2(上图)中使用了浅灰色和黑色)。

CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, donutSegment);
CGContextSetFillColorWithColor(c, [UIColor lightGrayColor].CGColor);
CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor);
CGContextDrawPath(c, kCGPathFillStroke);
于 2013-09-07T11:57:14.153 回答
2

如果我正确理解您的问题,您希望在中心有一个孔,并且您正在尝试使用剪贴蒙版来做到这一点。因此,您唯一需要做的就是反转您绘制尝试剪辑的内部路径的方向。然后,由于填充规则,它会为你切出一个很好的洞。

您的代码应如下所示:

CGPoint center = CGPointMake((self.bounds.size.width/2) + self.centerOffset, (self.bounds.size.height/2) - self.centerOffset);
CGFloat radius = MIN(center.x, center.y) - 25;
radius *= self.pieScale;
int clockwise = self.startAngle > self.endAngle;

/* Clipping should be done first so the next path(s) are not creating the clipping mask */
CGContextMoveToPoint(ctx, center.x, center.y);
/* Create outer path going clockwise */
CGContextAddArc(ctx, center.x, center.y, radius*3, 0, 2*M_PI, clockwise);
/* Create inner path / mask going counter-clockwise to make the 'hole' in the center */
CGContextAddArc(ctx, center.x, center.y, radius*0.5, 0, 2*M_PI, !clockwise);
CGContextClip(ctx);

/* Now, start drawing your graph and filling things in... */
CGContextBeginPath(ctx);
/* Here's the stroke of the inner circle */
CGPoint p1 = CGPointMake(center.x + radius/2 * cosf(self.endAngle), center.y + radius/2 * sinf(self.endAngle));
CGContextMoveToPoint(ctx, p1.x, p1.y);
CGContextAddArc(ctx, center.x, center.y, radius / 2 + 1, self.endAngle, self.startAngle, !clockwise);

p1 = CGPointMake(center.x + radius * cosf(self.startAngle), center.y + radius * sinf(self.startAngle));

CGContextAddLineToPoint(ctx, p1.x, p1.y);
CGContextAddArc(ctx, center.x, center.y, radius, self.startAngle, self.endAngle, clockwise);
CGContextClosePath(ctx);


CGContextSetFillColorWithColor(ctx, self.fillColor.CGColor);
CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
CGContextSetLineWidth(ctx, self.strokeWidth);

self.pathRef = CGContextCopyPath(ctx);
CGContextDrawPath(ctx, kCGPathFillStroke);

更新:我已经修复了代码并从您在 github 上的代码运行它并且它可以工作。希望这将解决您的“漏洞”问题。(请原谅双关语)

这是该技术示例的要点:https ://gist.github.com/rcdilorenzo/6437406

于 2013-09-04T14:07:37.930 回答