Search code examples
iosobjective-ccore-plot

Multiple (grouped) bars in CorePlot


I need to create a chart that groups 3 bars each x-axis, similar to this image:

enter image description here

The chart that I'm creating is getting like the image below:

enter image description here

My code:

constructEVDChart

- (void)constructEVDChart
{
    CPTXYGraph *newGraph = [[CPTXYGraph alloc] initWithFrame:CGRectZero];

    CPTTheme *theme = [CPTTheme themeNamed:@"Sales Farma"];
    [newGraph applyTheme:theme];

    self.hostingView.hostedGraph = newGraph;
    self.barChart = newGraph;

    newGraph.plotAreaFrame.masksToBorder = NO;
    newGraph.paddingLeft   = 60.0;
    newGraph.paddingTop    = 40.0;
    newGraph.paddingRight  = 20.0;
    newGraph.paddingBottom = 50.0;

    // Create grid line styles
    CPTMutableLineStyle *majorGridLineStyle = [CPTMutableLineStyle lineStyle];
    majorGridLineStyle.lineWidth = 1.0;
    majorGridLineStyle.lineColor = [[CPTColor whiteColor] colorWithAlphaComponent:0.15];

    // Add plot space for horizontal bar charts
    CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)newGraph.defaultPlotSpace;
    plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:@0.0 length:@30.0];
    plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:@0.0 length:@15.0];

    CPTXYAxisSet *axisSet = (CPTXYAxisSet *)newGraph.axisSet;
    CPTXYAxis *x          = axisSet.xAxis;
    {
        x.axisLineStyle       = nil;
        x.majorTickLineStyle  = nil;
        x.minorTickLineStyle  = nil;
        x.majorIntervalLength = @2.5;
        x.orthogonalPosition  = @0.0;
        x.majorGridLineStyle = majorGridLineStyle;

        // Define some custom labels for the data elements
        //x.labelRotation  = CPTFloat(M_PI_4);
        x.labelingPolicy = CPTAxisLabelingPolicyNone;
    }

    CPTNumberArray customTickLocations  = @[@1, @5, @10, @15];
    CPTStringArray xAxisLabels          = @[@"Label A", @"Label B", @"Label C", @"Label D"];
    NSUInteger labelLocation            = 0;

    NSNumber * vTickLocation = @1.5;

    CPTMutableAxisLabelSet customLabels = [NSMutableSet setWithCapacity:xAxisLabels.count];
    for ( NSNumber *tickLocation in customTickLocations ) {
        CPTAxisLabel *newLabel = [[CPTAxisLabel alloc] initWithText:xAxisLabels[labelLocation++] textStyle:x.labelTextStyle];
        newLabel.tickLocation = vTickLocation;
        newLabel.offset       = x.labelOffset + x.majorTickLength;
        [customLabels addObject:newLabel];

        vTickLocation = [NSNumber numberWithFloat:([vTickLocation floatValue] + 2.5)];
    }

    x.axisLabels = customLabels;

    CPTXYAxis *y = axisSet.yAxis;
    {
        y.axisLineStyle       = nil;
        y.majorTickLineStyle  = nil;
        y.minorTickLineStyle  = nil;
        y.majorIntervalLength = @5.0;
        y.orthogonalPosition  = @0.0;
        y.majorGridLineStyle = majorGridLineStyle;
    }

    // Profissional
    CPTBarPlot *profBar = [[CPTBarPlot alloc] init];
    profBar.dataSource = self;
    profBar.barOffset  = @0.50;
    profBar.barWidth   = @0.50;
    profBar.identifier = @"Bar Plot 1";
    profBar.delegate        = self;
    profBar.fill = [CPTFill fillWithColor:[CPTColor getCustomSFAColor:[UIColor sfaGraphOrangeColor]]];
    profBar.lineStyle = nil;
    [newGraph addPlot:profBar toPlotSpace:plotSpace];
    [profBar release];

    // PDV
    CPTBarPlot *pdvBar = [[CPTBarPlot alloc] init];
    pdvBar.dataSource      = self;
    pdvBar.barOffset       = @1.25;
    pdvBar.barWidth        = @0.50;
    pdvBar.identifier      = @"Bar Plot 2";
    pdvBar.delegate        = self;
    pdvBar.fill = [CPTFill fillWithColor:[CPTColor getCustomSFAColor:[UIColor sfaGraphBlueColor]]];
    pdvBar.lineStyle = nil;
    [newGraph addPlot:pdvBar toPlotSpace:plotSpace];
    [pdvBar release];

    // Estab
    CPTBarPlot *estabBar = [[CPTBarPlot alloc] init];
    estabBar.dataSource      = self;
    estabBar.barOffset       = @2.00;
    estabBar.barWidth        = @0.50;
    estabBar.identifier      = @"Bar Plot 3";
    estabBar.delegate        = self;
    estabBar.fill = [CPTFill fillWithColor:[CPTColor getCustomSFAColor:[UIColor sfaGraphGreenColor]]];
    estabBar.lineStyle = nil;
    [newGraph addPlot:estabBar toPlotSpace:plotSpace];
    [estabBar release];
}

numberOfRecordsForPlot

- (NSUInteger)numberOfRecordsForPlot:(nonnull CPTPlot *)plot
{
    return 4;
}

numberForPlot

- (nullable id)numberForPlot:(nonnull CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    NSNumber *num = nil;

    switch ( fieldEnum ) {
        case CPTBarPlotFieldBarLocation:
        {
            num = @(index);
            break;
        }
        case CPTBarPlotFieldBarTip:
            num = @( (index + 1) * 2 );
            break;
    }

    return num;
}

dataLabelForPlot

- (nullable CPTLayer *)dataLabelForPlot:(nonnull CPTPlot *)plot recordIndex:(NSUInteger)index
{
    static CPTMutableTextStyle *whiteText = nil;
    static dispatch_once_t onceToken      = 0;

    dispatch_once(&onceToken, ^{
        whiteText = [[CPTMutableTextStyle alloc] init];
        whiteText.color = [CPTColor whiteColor];
    });

    CPTTextLayer *newLayer = nil;

    if ( [plot isKindOfClass:[CPTPieChart class]] ) {
        switch ( index ) {
            case 0:
                newLayer = (id)[NSNull null];
                break;

            default:
                newLayer = [[CPTTextLayer alloc] initWithText:[NSString stringWithFormat:@"%lu", (unsigned long)index] style:whiteText];
                break;
        }
    }
    else if ( [plot isKindOfClass:[CPTScatterPlot class]] ) {
        newLayer = [[CPTTextLayer alloc] initWithText:[NSString stringWithFormat:@"%lu", (unsigned long)index] style:whiteText];
    }

    return newLayer;
}

Solution

  • The bars are one unit apart, but since they each have a width of 0.5, they will overlap no matter what you do. To match the example plot, make the width of each bar 0.25. This lets all three plots be together with a gap of 0.25 between each group. Make the bar width slightly smaller (e.g., 0.2) to leave a small gap between adjacent bars, too. I would use bar offsets of -0.25, 0.0, and 0.25. This would center the grouping over the bar location and leave some space between groups.

    The data labels aren't showing up because -dataLabelForPlot:recordIndex: always returns nil. The plot is a CPTBarPlot while the data label method only creates labels for pie charts and scatter plots.