0
votes

I'm trying to create a class that I can use to create bar, pie, and line graphs using the Core Plot library for an iOS application. I want to create some standardization in this class so that I only have to change a few options for each view controller that inherits my graph class. I have the pie graphs working the way I want, but I'm having issues with the bar graphs. I want a bar graph that is horizontal and where the Y axis is fixed for each plot based on how many plots there are.

Code:

enum
{
    PieGraph = 0,
    LineGraph,
    BarGraph
};

Graph.h:

#import "CorePlot-CocoaTouch.h"
#import "BaseViewController.h"

CPTGraphHostingView *hostView;

CPTTheme *selectedTheme;

/*************** graphing options *******************/

int graphType;
int fillPercentage;

CGFloat xMax;
CGFloat yMax;

NSString *graphTitle;
NSString *xAxisTitle;
NSString *yAxisTitle;

BOOL graphOnBottom;

@interface Graph : BaseViewController<CPTPlotDataSource, UIActionSheetDelegate, CPTBarPlotDataSource, CPTBarPlotDelegate>

/*************** Methods *****************/


// graphing methods
-(void)initPlot;
-(void)configureHost;
-(void)configureGraph;
-(void)configureChart;
-(void)configureLegend;
-(void)configurePlots;
-(void)configureAxes;

@end

Graph.m:

#import "Graph.h"

@implementation Graph

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [self initPlot];
}

/************************ Methods *************************/

// initialize the entire graph plot
-(void)initPlot
{
    [self configureHost];
    [self configureGraph];
    if (graphType == PieGraph)
    {
        [self configureChart];
        [self configureLegend];
    }
    else if (graphType == BarGraph)
    {
        [self configurePlots];
        [self configureAxes];
    }
}

// configure the host
-(void)configureHost
{
    CGRect parentRect = self.view.bounds;

    int maxHeight = (parentRect.size.height - 55);
    int height = (maxHeight - ((100 - fillPercentage) * maxHeight / 100));

    int yPosition;
    if (graphOnBottom) yPosition = (maxHeight - height + 55);
    else yPosition = 55;

    parentRect = CGRectMake(parentRect.origin.x, yPosition, parentRect.size.width, height);

    hostView = [(CPTGraphHostingView *) [CPTGraphHostingView alloc] initWithFrame:parentRect];
    hostView.allowPinchScaling = NO;

    [self.view addSubview:hostView];
}

// configure the graph
-(void)configureGraph
{
    CPTGraph *graph = [[CPTXYGraph alloc] initWithFrame:hostView.bounds];
    hostView.hostedGraph = graph;

    if (graphType == PieGraph)
    {
        graph.paddingLeft = 0.0f;
        graph.paddingTop = 0.0f;
        graph.paddingRight = 0.0f;
        graph.paddingBottom = 0.0f;
        graph.axisSet = nil;
    }
    else if (graphType == BarGraph)
    {
        graph.plotAreaFrame.masksToBorder = NO;

        if ([xAxisTitle isEqualToString:@""]) graph.paddingBottom = 0.0f;
        else graph.paddingBottom = 30.0f;
        if ([yAxisTitle isEqualToString:@""]) graph.paddingLeft = 0.0f;
        else graph.paddingLeft = 30.0f;
        graph.paddingTop = 0.0f;
        graph.paddingRight = 0.0f;

        CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *) graph.defaultPlotSpace;
        plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromCGFloat(0.0f) length:CPTDecimalFromCGFloat(xMax)];
        plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromCGFloat(0.0f) length:CPTDecimalFromCGFloat(yMax)];
    }
    else if (graphType == LineGraph)
    {

    }

    CPTMutableTextStyle *textStyle = [CPTMutableTextStyle textStyle];
    textStyle.color = [CPTColor blackColor];
    textStyle.fontName = @"Helvetica-Bold";
    textStyle.fontSize = 12.0f;

    graph.title = graphTitle;
    graph.titleTextStyle = textStyle;
    graph.titlePlotAreaFrameAnchor = CPTRectAnchorTop;

    selectedTheme = [CPTTheme themeNamed:kCPTPlainWhiteTheme];
    [graph applyTheme:selectedTheme];
}

// configure the chart
-(void)configureChart
{
    CPTGraph *graph = hostView.hostedGraph;

    CPTGradient *overlayGradient = [[CPTGradient alloc] init];
    overlayGradient.gradientType = CPTGradientTypeRadial;
    overlayGradient = [overlayGradient addColorStop:[[CPTColor blackColor] colorWithAlphaComponent:0.0] atPosition:0.9];
    overlayGradient = [overlayGradient addColorStop:[[CPTColor blackColor] colorWithAlphaComponent:0.4] atPosition:1.0];

    CPTPieChart *pieChart = [[CPTPieChart alloc] init];

    pieChart.dataSource = self;
    pieChart.delegate = self;
    pieChart.pieRadius = (hostView.bounds.size.height * 0.7) / 2.5;
    pieChart.identifier = graph.title;
    pieChart.startAngle = M_PI_4;
    pieChart.sliceDirection = CPTPieDirectionClockwise;

    pieChart.overlayFill = [CPTFill fillWithGradient:overlayGradient];

    [graph addPlot:pieChart];
}

// configure plots
-(void)configurePlots
{
    CPTGraph *graph = hostView.hostedGraph;

    if (graphType == BarGraph)
    {
        CPTMutableLineStyle *barLineStyle = [[CPTMutableLineStyle alloc] init];
        barLineStyle.lineColor = [CPTColor lightGrayColor];
        barLineStyle.lineWidth = 0.5;

        CPTBarPlot *bar = [CPTBarPlot tubularBarPlotWithColor:[CPTColor redColor] horizontalBars:YES];

        bar.dataSource = self;
        bar.delegate = self;
        bar.barWidth = CPTDecimalFromFloat(2.5f);
        bar.barOffset = CPTDecimalFromFloat(2.5f);
        bar.identifier = @"redBar";

        bar.lineStyle = barLineStyle;

        CPTBarPlot *bar2 = [CPTBarPlot tubularBarPlotWithColor:[CPTColor greenColor] horizontalBars:YES];

        bar2.dataSource = self;
        bar2.delegate = self;
        bar2.barWidth = CPTDecimalFromFloat(2.5f);
        bar2.barOffset = CPTDecimalFromFloat(2.5f);
        bar2.identifier = @"greenBar";

        bar2.lineStyle = barLineStyle;

        [graph addPlot:bar toPlotSpace:graph.defaultPlotSpace];
        [graph addPlot:bar2 toPlotSpace:graph.defaultPlotSpace];
    }
    else if (graphType == LineGraph)
    {

    }
}

// configure axes
-(void)configureAxes
{
    if (graphType == BarGraph)
    {
        CPTMutableTextStyle *axisTitleStyle = [CPTMutableTextStyle textStyle];
        axisTitleStyle.color = [CPTColor blackColor];
        axisTitleStyle.fontName = @"Helvetica-Bold";
        axisTitleStyle.fontSize = 12.0f;
        CPTMutableLineStyle *axisLineStyle = [CPTMutableLineStyle lineStyle];
        axisLineStyle.lineWidth = 2.0f;
        axisLineStyle.lineColor = [[CPTColor blackColor] colorWithAlphaComponent:1];

        CPTXYAxisSet *axisSet = (CPTXYAxisSet *) hostView.hostedGraph.axisSet;

        axisSet.xAxis.labelingPolicy = CPTAxisLabelingPolicyNone;
        axisSet.xAxis.title = xAxisTitle;
        axisSet.xAxis.titleTextStyle = axisTitleStyle;
        axisSet.xAxis.titleOffset = 5.0f;
        axisSet.xAxis.axisLineStyle = axisLineStyle;

        axisSet.yAxis.labelingPolicy = CPTAxisLabelingPolicyNone;
        axisSet.yAxis.title = yAxisTitle;
        axisSet.yAxis.titleTextStyle = axisTitleStyle;
        axisSet.yAxis.titleOffset = 5.0f;
        axisSet.yAxis.axisLineStyle = axisLineStyle;
    }
    else if (graphType == LineGraph)
    {

    }
}

// configure the legend
-(void)configureLegend
{
    CPTGraph *graph = hostView.hostedGraph;

    CPTLegend *legend = [CPTLegend legendWithGraph:graph];

    legend.numberOfColumns = 1;
    legend.fill = [CPTFill fillWithColor:[CPTColor whiteColor]];
    legend.borderLineStyle = [CPTLineStyle lineStyle];
    legend.cornerRadius = 5.0;

    graph.legend = legend;
    graph.legendAnchor = CPTRectAnchorBottomLeft;
    //CGFloat legendPadding = -(self.view.bounds.size.width / 8);
    graph.legendDisplacement = CGPointMake(3.0, 3.0);
}

@end

And finally the view controller class I am testing all of this on:

TestView.h:

#import "Graph.h"

NSArray *channel;

int offline;
int online;

@interface TestView : Graph

/*************** Methods *****************/

// back button
- (IBAction)backButtonClicked:(id)sender;

// handle view load
-(void) handleViewLoad;

// set arrays
-(void)setArrays;

@end

TestView.m:

#import "TestView.h"

@interface TestView ()

@end

@implementation TestView

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    [self handleViewLoad];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)backButtonClicked:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:nil];
}

/*************** graph things ****************/

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

-(NSNumber*)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx
{
    int returnVal = 0;
    NSLog([NSString stringWithFormat:@"idx: %d", idx]);
    /*switch (idx)
    {
        case 0:
            for (int x = 0; x < [channel count]; x++)
            {
                if ([channel[x] integerValue] == 20) returnVal ++;
            }
            offline = returnVal;
            return [NSNumber numberWithInt:returnVal];
            break;

        case 1:
            for (int x = 0; x < [channel count]; x++)
            {
                if ([channel[x] integerValue] != 20) returnVal ++;
            }
            online = returnVal;
            return [NSNumber numberWithInt:returnVal];
            break;
    }*/
    if ([plot.identifier isEqual:@"greenBar"])
    {
        for (int x = 0; x < [channel count]; x++)
        {
            if ([channel[x] integerValue] != 20) returnVal ++;
        }
        online = returnVal;
        return [NSNumber numberWithInt:returnVal];
    }
    else if ([plot.identifier isEqual:@"redBar"])
    {
        for (int x = 0; x < [channel count]; x++)
        {
            if ([channel[x] integerValue] == 20) returnVal ++;
        }
        offline = returnVal;
        return [NSNumber numberWithInt:returnVal];
    }
    return 0;
}

-(CPTLayer*)dataLabelForPlot:(CPTPlot *)plot recordIndex:(NSUInteger)idx
{
    /*switch (idx)
    {
        case 0:
            return [[CPTTextLayer alloc] initWithText:[NSString stringWithFormat:@"Offline: %d", offline]];
            break;

        default:
            return [[CPTTextLayer alloc] initWithText:[NSString stringWithFormat:@"Online: %d", online]];
            break;
    }*/

    if ([plot.identifier isEqual:@"greenBar"])
    {
        return [[CPTTextLayer alloc] initWithText:[NSString stringWithFormat:@"Online: %d", online]];
    }
    else if ([plot.identifier isEqual:@"redBar"])
    {
        return [[CPTTextLayer alloc] initWithText:[NSString stringWithFormat:@"Offline: %d", offline]];
    }
    return nil;
}

-(NSString*)legendTitleForPieChart:(CPTPieChart*)pieChart recordIndex:(NSUInteger)idx
{
    switch (idx)
    {
        case 0:
            return @"Offline";
            break;

        default:
            return @"Online";
            break;
    }

    return @"";
}

// handle view load
-(void)handleViewLoad
{
    session = [BaseViewController getSession];
    [self setArrays];

    graphType = BarGraph;
    fillPercentage = 50;
    graphOnBottom = NO;
    graphTitle = @"Coordinators Online/Offline";
    xAxisTitle = @"Number of Devices";
    yAxisTitle = @"";
    xMax = (CGFloat)([channel count] + ([channel count] * 0.30f));
    yMax = (CGFloat)([channel count] + ([channel count] * 0.30f));

    [self initPlot];
}

// set arrays
-(void)setArrays
{
    channel = [Json extractCoordinatorChannel:session];
}

@end

Here are some examples of what my graphs look like:

enter image description here

enter image description here

As you can see, the issue is that both the x and y axes are changing based on the value. I need only the x axis to change, and the y axis to be fixed for each plot.

1
I would recommend first writing a simple example project just for the bar chart, get it looking the way you want and then integrating that into this more abstract project. Maybe start with the example code that comes with core-plot? There is quite a lot of code to go through here so you will probably get an answer if you simplify your question.Daniel Farrell

1 Answers

0
votes

The datasource should check the fieldEnum parameter to see which data field (bar location or bar tip) is being requested.

switch ( fieldEnum ) {
    case CPTBarPlotFieldBarLocation:
        returnVal = /* y value */;
        break;

    case CPTBarPlotFieldBarTip:
        returnVal = /* x value */;
        break;

    default:
        break;
}