Donut chart with custom legend title


#1

Hi all,

I’m implementing a donut chart with a legend that currently shows just the datapoint name.

I need to customize the legend item title showing not only the datapoint name but also its value.
What I tried to do is to subclass SChartDonutSeries to override the implementation of titleForSliceInLegend: to something like this:

- (NSString *)titleForSliceInLegend:(NSInteger)index {
    NSString *title = nil;
    id<SChartData> dataPoint = self.dataSeries.dataPoints[index];
    if (dataPoint && [dataPoint isKindOfClass:[SChartRadialDataPoint class]]) {
        SChartRadialDataPoint *dp = (SChartRadialDataPoint *)dataPoint;

        if (dp && dp.name && dp.value) {
            NSString *valueStr;
            if (_formatter) {
                valueStr = [_formatter stringFromNumber:dp.value];
            } else {
                valueStr = [NSString stringWithFormat:@"%f", dp.value.doubleValue];
            }

            title = [NSString stringWithFormat:@"%@ - %@", dp.name, valueStr];
        }
    }

    return title;
}

The problem is that the dataPoint object is an instance of SChartInternalDataPoint, which is not a subclass of SChartRadialDataPoint, of course.

How do I access to the datapoint set by the chart delegate (a SChartRadialDataPoint)?

Secondary question: how can I have the legend item title be multilined?

Thanks!

Alberto


#2

Hi Punkers,

  1. You can get the name and value from the dataSeries dataPoints array using the SChartData protocol.

As the dataPoints array is an array of objects implementing the SChartData protocol.

You can retrieve the data point’s name via the “sChartXValue” method & value from “sChartYValue” method.

Here is some sample code:

for(id<SChartData>dp in series.dataSeries.dataPoints) {
    NSLog(@"Name: %@, Value: %@", [dp sChartXValue], [[dp sChartYValue] stringValue]);
}
  1. You can access each legend item’s label via the labels array property on the legend.

You can loop through these setting the “numberOfLines” property on it to zero.

Let me know if you have any questions.

Kind regards,
Andrew Polkinghorn.


#3

Hi Andrew,

thanks for your reply!

Your suggestion on my first problem works perfectly, thank you.

Regarding the multilined legend items, though, when should I access the legend object? I tried in the delegate method

- (void)sChartRenderFinished:(ShinobiChart *)chart

but at that moment the labels array is empty.

Thanks again

Alberto


#4

Hi Alberto,

The “sChartRenderFinished” delegate method is where I would recommend to check the legend labels.

I have just checked and the labels array should not be empty when entering this method.

Can you check you’ve set your legend’s hidden property to NO before your implementation of the render finished delegate method is called.

If you can replicate your issue in one of our sample apps could you send it to us?

It would really help us identify the source of your issue.

Kind regards,
Andrew Polkinghorn.


#5

Ok, the problem of the empty labels array was indeed that the legend was hidden when the delegate method was called (I did set legend.hidden = NO in the method itself).

Anyway, not even setting numberOfLines = 0 makes the legend item multilined.


#6

Hi Alberto,

You’ll have to adjust the size of the labels to fit the new multi-lined text.

Calling the UILabel’s “sizeToFit” method should do this for you, but you may have to reposition the label to be centred alongside the legend symbol.

Kind regards,
Andrew Polkinghorn.


#7

Hi Andrew,

I can’t make it work… I set up a simple project with just a donut chart and very long labels (with a very big font) and implementing the render finished method like this:

- (void)sChartRenderFinished:(ShinobiChart *)chart {
    SChartLegend *legend = chart.legend;

    for (UILabel * l in legend.labels) {
        l.numberOfLines = 0;
        [l sizeToFit];
    }
}

but this is what I get

What am I missing?

Thanks,
Alberto


#8

Hi Alberto,

After further investigation it looks like the best way to implement multi-line labels is to create your own custom legend.

Please find below an implementation of a CustomLegend that should provide you with multi-line labels you are after:

@interface CustomLegend : SChartLegend
@end

@implementation CustomLegend

-(void)drawLegend {
    self.autosizeLabels = NO;
    
    CGRect frame = self.frame;
    frame.size = CGSizeMake(self.chart.bounds.size.width, 200); // Set desired legend size.
    self.frame = frame;
    
    [super drawLegend];
    
    double legendWidth = self.frame.size.width - (2 * [self.style.borderWidth doubleValue]);
    double totalHorizontalPadding = ([self.style.horizontalPadding doubleValue] * ((self.maxSeriesPerLine * 2) + 1));
    double totalSymbolWidths = self.maxSeriesPerLine * [self.symbolWidth doubleValue];
    double lblWidth = (legendWidth - totalHorizontalPadding - totalSymbolWidths) / 3;
    
    for (UILabel *lbl in self.labels) {
        CGRect frame = lbl.frame;
        frame.size.width = lblWidth;
        lbl.frame = frame;
        lbl.numberOfLines = 0;
    }
}

@end

Make sure you set your legend on your chart like so:

chart.legend = [[CustomLegend alloc] initWithChart:chart];

Let me know if you have any questions.

Kind regards,
Andrew Polkinghorn.


#9

Thank you Andrew, the custom legend is the way to go!

Alberto