Adjacent CAShapeLayer Antialiasing Issue

1k views Asked by At

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.

https://i.stack.imgur.com/Qdu8d.png

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.

2

There are 2 answers

0
Aaron On BEST ANSWER

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:

The filler lines with no slices drawn.

And here's the final product:

Final result with lines underneath and slices on top.

2
rob mayoff On

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:

first slice

Then draw the second slice as a full disc, except for the proper area of the first slice:

second slice

Then draw the third slice as a full disc, except for the proper area of the first two slices:

third slice

And so on:

fourth slice fifth slice

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