I'm drawing a pie chart using a CAShapeLayer for each slice of the pie. Even when the end angle of one pie slice is equal to the start angle of the adjacent slice, antialiasing is resulting in the underlying background color appearing between each pice slice if the border between slices is at an angle.
I'd like to eliminate the slight gap between slices while still using antialiasing so the resulting pie cart still looks smooth. Conceptually, it seems if there were a way to apply antialiasing to the entire CALayer and it's pie slice sublayers after all the pie slices were drawn, that would do the trick... The pie slices would be antialiased into each other instead of into the background.
I've played around with as many CALayer properties as I can think of and am having a hard time finding more information on this. Any ideas?
UPDATE: See my answer below.
You're probably not going to be able to make your pie slice edges meet up exactly with no cracks. The simplest solution is not to try.
Instead of making your pie slices meet at the edges, make them overlap. Draw the first slice as a full disc:
Then draw the second slice as a full disc, except for the proper area of the first slice:
Then draw the third slice as a full disc, except for the proper area of the first two slices:
And so on:
Here's my code:
#import "PieView.h"
@implementation PieView {
NSMutableArray *slices;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
[self layoutSliceLayers];
}
- (void)layoutSliceLayers {
if (slices == nil) {
[self createSlices];
}
[slices enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self layoutSliceLayer:obj index:idx];
}];
}
static const int kSliceCount = 5;
- (void)createSlices {
slices = [NSMutableArray arrayWithCapacity:kSliceCount];
for (int i = 0; i < kSliceCount; ++i) {
[slices addObject:[self newSliceLayerForIndex:i]];
}
}
- (CAShapeLayer *)newSliceLayerForIndex:(int)i {
CAShapeLayer *layer = [CAShapeLayer layer];
layer.fillColor = [UIColor colorWithWhite:(CGFloat)i / kSliceCount alpha:1].CGColor;
[self.layer addSublayer:layer];
return layer;
}
- (void)layoutSliceLayer:(CAShapeLayer *)layer index:(int)index {
layer.position = [self center];
layer.path = [self pathForSliceIndex:index].CGPath;
}
- (CGPoint)center {
CGRect bounds = self.bounds;
return CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
}
- (UIBezierPath *)pathForSliceIndex:(int)i {
CGFloat radius = [self radius];
CGFloat fudgeRadians = 5 / radius;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointZero
radius:radius startAngle:2 * M_PI * i / kSliceCount
endAngle:2 * M_PI clockwise:YES];
[path addLineToPoint:CGPointZero];
[path closePath];
return path;
}
- (CGFloat)radius {
CGSize size = self.bounds.size;
return 0.9 * MIN(size.width, size.height) / 2;
}
@end
UPDATE: Rob's answer is pretty good, but possibly results in other antialiasing issues. I ended up 'filling' the gaps by drawing 1pt wide radial lines along the end angle of each pie slice, each line being the same color as the adjacent slice. These lines are drawn at a lower z index than the pie slices, so they are underneath. Here's what they look like without the pie slices drawn on top:
And here's the final product:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With