CALayer clip CGContextAddArc (create slide show / pie chart)

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

The pie chart works great and looks great, but I have problems with clipping the inner circle.

This 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 is as follows:

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

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

I am trying to copy the second path using CGContextClip(ctx); but only leaves me with an inner circle like this:

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

It seems to me that this is happening, but I can’t understand what else I should do.

Edit:

The code now looks like this:

 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); 

It looks like:

enter image description here

All drawing code:

My class is a subclass of CALayer. This code draws one piece in a 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
source share
2 answers

If I understand your question correctly, you want to have a hole in the center, and you are trying to use a clipping mask for this. The only thing you need to do is reverse the direction in which you draw the inner path that you were trying to fix. Then, due to the filling rules, it cuts a nice hole for you.

Your code should look something like this:

 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 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); 

Update: I fixed the code and ran it from my code on github and it works. Hope this fixes your hole problem. (sorry for the pun)

Here is the gist of an example of this technique: https://gist.github.com/rcdilorenzo/6437406

+2
source

If you don’t want to have the center of the pie chart (i.e. make a so-called donut basket), I would suggest creating shapes that are separate segments of the donut, instead of making pieces of the cake and mask the center.

The first thing that may come to mind is to create this segment from two straight lines and two arcs with different radii, but with the same central coordinate. Fortunately, there are simpler ways to make this form in Core Graphics. This shape is just one arc between one corner and another, but thicker. I explained all this in this answer (to the question "Draw segments from a circle or donut"), but here is a short explanation of only the shape of the donut segment.

Step 1: Create an Arc

Create an arc that will be in the center of the donut segment (orange line in the image below). The radius will be (r max + r min ) / 2 .

enter image description here

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

Step 2: Stroke That Arc

Create the final shape of the donut segment by stroking the arc. This feature may look like magic to you, but it is how it feels to find hidden treasure in Core Graphics. It will move the path with a certain stroke width. The “linear cover” and “line connection” control how the beginning and end of the form look and how the connections between the components of the path are connected (there is only one component in this form).

enter image description here

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

Step 3: There is no step 3 (well, there is a fill + move)

Fill this shape in the same way as you did with the pie shapes. (lightGray and black were used in image 2 (see above)).

 CGContextRef c = UIGraphicsGetCurrentContext(); CGContextAddPath(c, donutSegment); CGContextSetFillColorWithColor(c, [UIColor lightGrayColor].CGColor); CGContextSetStrokeColorWithColor(c, [UIColor blackColor].CGColor); CGContextDrawPath(c, kCGPathFillStroke); 
+7
source

Source: https://habr.com/ru/post/1500534/


All Articles