Blog

Back to Blog

Pie chart labels that are out of this … slice

Posted on 6 Aug 2012 Written by Stuart Grey

It’s been a while since we published this post, and both iOS and ShinobiCharts have moved on… but the good news is that we’ve got an updated version of the project on GitHub where you can browse, clone, or fork the project, or simply download the zip.

One of the most common requests we get related to our pie and donut charts is about labels. Out-of-the-box they display the value of the slice, which you can format nicely with a format string – but many of you want more and we’re here to deliver. In this post, I’ll explain how to fully customize the labels and even move them out of the slices! Let’s take a look at a before and after to see where we’re going with this.

 

pie_labels_before        pie_labels_after

 

Taking control

The first thing you’ll want to do is get access to the labels for each slice and there is a delegate call for just that. Make sure you have your chart delegate set to somewhere and add the following method:

-(void)sChart:(ShinobiChart *)chart alterLabel:(UILabel *)label forDatapoint:(SChartDataPoint *)datapoint atSliceIndex:(int)index inRadialSeries:(SChartRadialSeries *)series

You’ll notice that for each slice we’re going to be given a label that is about to be added to the chart – what we do with it is now a decision completely in our hands. 

Saying what you need to

Our first task is to populate our label with some useful data. Let’s use the name parameter you set on the datapoint for now. However, since we’re setting the text directly, the world is your oyster…

This makes the body of our method look like this:

-(void)sChart:(ShinobiChart *)chart alterLabel:(UILabel *)label forDatapoint:(SChartDataPoint *)datapoint atSliceIndex:(int)index inRadialSeries:(SChartRadialSeries*)series

{
    [label setText:datapoint.xValue];
    [label sizeToFit];
    [label setHidden:NO];
    
    return label;
}

 

Position is everything

Next we’re going to move our label. We can set the position of the label to any set of values we like, but to keep it relevant we’ll simply move them all outwards. This requires a little maths – but the code below will move the center of the label out relative to the angle of the slice. If you’re using the PieSample project then I’d recommend setting the outerRadius on your pie series to be 100.f to give us some room. We’ve also included an EXTRUSION value to allow some tweaking:

#define EXTRUSION 150.
-(void)sChart:(ShinobiChart *)chart alterLabel:(UILabel *)label forDatapoint:(SChartDataPoint *)datapoint atSliceIndex:(int)index inRadialSeries:(SChartRadialSeries *)series {
    
    SChartPieSeries *pieSeries = (SChartPieSeries *)series;
    
    //get our radial point from our datasource method
    
    // three points:
    CGPoint pieCenter;      // chart center for trig calculations
    CGPoint oldLabelCenter; // original label center
    CGPoint labelCenter;    // new label center
    
    pieCenter = [pieSeries getDonutCenter];  
    labelCenter = [pieSeries getSliceCenter:index];
    
    // find the angle of the slice, and add on a little to the label's center
    float xChange, yChange;
    
    xChange = pieCenter.x - labelCenter.x;
    yChange = pieCenter.y - labelCenter.y;
    
    float angle = atan2f(xChange, yChange) + M_PI / 2.f;
    // we do the M_PI / 2 adjustment because of how the pie is drawn internally
    
    labelCenter.x = labelCenter.x + EXTRUSION * cosf(angle);
    labelCenter.y = labelCenter.y - EXTRUSION * sinf(angle);    
    
    
    [label setText:datapoint.xValue];
    [label sizeToFit];
    [label setCenter:labelCenter]; // this must be after sizeToFit
    [label setHidden:NO];
    
}

When we run this you’ll see that the labels now float around outside of our pie chart but still relative to their slice.

Connecting it all together

Our labels look a little lost out there all alone!  Let’s connect them back to their slices with a line. We’ve kept this very simple – but since this is done in a drawRect: method you have plenty of drawing options available. (Check out the body of the LineView class – introduced below – to see the drawing code)

We’re going to place a new UIView subclass over our chart that is capable of connecting up pairs of points with a line. The code for this is available in the LineView is available in the full zip at the end of the blog. Add this file to your project and create an ivar called lineView in your view controller.

At the end of our viewDidLoad: method, add the following code to configure our lineView object and place it on the chart:

lineView = [[LineView alloc] init];
[lineView setUserInteractionEnabled:NO];
[lineView setBackgroundColor:[UIColor clearColor]];
[pieChart addSubview:lineView];

Next we’ll need to add some coordinates for our lines to connect. Our delegate method now has a new (old!) point called oldLabelCenter and some code to add the points to our lineView:

#define EXTRUSION 150.
-(void)sChart:(ShinobiChart *)chart alterLabel:(UILabel *)label forDatapoint:(SChartDataPoint *)datapoint atSliceIndex:(int)index inRadialSeries:(SChartRadialSeries *)series {
    
    SChartPieSeries *pieSeries = (SChartPieSeries *)series;
    
    //get our radial point from our datasource method
    
    // three points:
    CGPoint pieCenter;      // chart center for trig calculations
    CGPoint oldLabelCenter; // original label center
    CGPoint labelCenter;    // new label center
    CGPoint endOfLine;     // we want our line to finish just short of our label
    
    pieCenter = [pieSeries getDonutCenter];  
    oldLabelCenter = labelCenter = [pieSeries getSliceCenter:index];
    
    // find the angle of the slice, and add on a little to the label's center
    float xChange, yChange;
    
    xChange = pieCenter.x - labelCenter.x;
    yChange = pieCenter.y - labelCenter.y;
    
    float angle = atan2f(xChange, yChange) + M_PI / 2.f;
    // we do the M_PI / 2 adjustment because of how the pie is drawn internally
    
    labelCenter.x = oldLabelCenter.x + EXTRUSION * cosf(angle);
    labelCenter.y = oldLabelCenter.y - EXTRUSION * sinf(angle);    
    
    endOfLine.x = oldLabelCenter.x + (EXTRUSION-30.f) * cosf(angle);
    endOfLine.y = oldLabelCenter.y - (EXTRUSION-30.f) * sinf(angle); 
    
    [label setText:datapoint.xValue];
    [label sizeToFit];
    [label setCenter:labelCenter]; // this must be after sizeToFit
    [label setHidden:NO];
    
    // connect our old label point to our new label
    [lineView addPointPair:oldLabelCenter second:endOfLine forLabel:label];
}

We haven’t quite finished yet – we now need to trigger the draw when our chart changes with another delegate call:

- (void) sChartRenderStarted:(ShinobiChart *)chart withFullRedraw:(BOOL)fullRedraw {
    // position our view over the top of the GL canvas
    CGRect glFrame = chart.canvas.glView.frame;
    glFrame.origin.y = chart.canvas.frame.origin.y;
    [lineView setFrame:glFrame];
    // remove the old point-pairs from the line view
    [lineView reset];
}

 For this code to work you’ll need to import the canvas header file:

#import <ShinobiCharts/SChartCanvas.h>

If you run this project, you should now have a pie chart with external labels and some lines connecting them to thier slices. Check it out!

Wrapping it up

One last tip before we go – try setting the selectedPosition on the pie series to zero. You’ll notice that our custom lines and labels adopt all of the animations. Cool huh! You can download the fully updated PieChart sample app here. Have fun customising those lines and do get in touch with your creations!

Back to Blog