Bar/histogram on core-plot

I’m playing with core-plot recently :) I wanted to draw a bar plot such as this,

The first problem I had was the default settings gave me bars that are too thin. I wanted bars that are bit wider but not too wide. The slimmest bars would be strips of lines. The widest bars would take up a full slot, for example one bar would start on the 0 mark on the x-axis and end on the 1 mark, the other starts on 1 and ends on 2, etc. Wide bars may be interesting to some people though. I thought they make my graph look too cluttered so I decided that a reasonable width would be half of the distance between successive marks. My second requirement was the marks on the x-axis should divide bars into two equal halves (as shown). This post is how I managed to do this particular task. I don’t put a full code listing here but I’m happy to do so if anyone is interested. There is some code listing in my previous post about my first go on line plot. If you are serious about core-plot, having the API will be useful.

Create the bar plot first,

CPBarPlot* barPlot = [[[CPBarPlot alloc] initWithFrame:self.mGraph.defaultPlotSpace.graph.bounds] autorelease];
barPlot.identifier = @"MyBarPlot";
barPlot.dataSource = self;

To set the width of bars we need to first find the length of the x-axis the total length of the x-axis. The first line below takes the total width of the iphone screen, which is 320 (assuming portrait here). Graphs have left and right paddings. The second line subtracts those.


double spaceAvailableOnXaxis =320;
spaceAvailableOnXaxis = spaceAvailableOnXaxis - (self.mGraph.paddingLeft + self.mGraph.paddingRight);

We now set the width of a bar. Because each bar is half as wide as the distance between successive marks, the width would be the length of the x-axis divided by twice the number of marks. I put “4″ in the code below because my x-axis has four marks.


barPlot.barWidth = spaceAvailableOnXaxis/(4*2);

Next we need to tell core-plot where to start plotting the bars. The tricky part here is this should be specified in terms of bar width. So if you specify “3″, the first bar will be drawn at a distance (3*barwidth) from the beginning of the x-axis, and not at a distance of 3.0. In the graph shown above, I want the first bar to appear at mark 1 on the x-axis. As we said earlier, the distance between 0 and 1, which is the distance between successive marks, is twice the width of a bar. So we do,


barPlot.barOffset = 2;

We now have this graph

What gives? Before all this story started, I had to set the length of my x-axis. I did,

double xAxisLengthOriginal = 4.0; //ignore the "Original" part of the variable for now, it'll become clear soon
CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)self.mGraph.defaultPlotSpace;
plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)
length:CPDecimalFromFloat(xAxisLengthOriginal)];

It is a good idea to consider the datapoints being plotted to decide how long the x-axis should be so as to make good use of the space available. But the the effect of leaving the length of the x-axis to the maximum value of our x-axis data (in this case 4.0) results in a graph that goes right up to the boundary. In the bar plot we have here it certainly is not desirable. Even in a line plot, the right most data point in this scenario will be too far to the right that it looks as if some part of the graph was cut out. One way of leaving slack space is to do as such,

double xAxisLengthOriginal = 4.0;
xAxisLength = xAxisLengthOriginal + 0.2*xAxisLengthOriginal

The result is this.

We have extra space as planned. But because the length of the x-axis has changed, it is messing up our previous calculation of the bar width. The bars are a little bit wider than we want them to be. So before setting the bar width we add the line,

spaceAvailableOnXaxis = spaceAvailableOnXaxis*(xAxisMaxOriginal/xAxisMax);

This reverses the effects of our earlier “slack space surgery”. So to summarize, prepare the plot space:

double xAxisLengthOriginal = 4.0;
xAxisLength = xAxisLengthOriginal + 0.2*xAxisLengthOriginal
CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)self.mGraph.defaultPlotSpace;
plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)
length:CPDecimalFromFloat(xAxisLength)];
...

Then the bar plot:

CPBarPlot* barPlot = [[[CPBarPlot alloc] initWithFrame:self.mGraph.defaultPlotSpace.graph.bounds] autorelease];
barPlot.identifier = @"MyBarPlot";
barPlot.dataSource = self;
double spaceAvailableOnXaxis =320;
spaceAvailableOnXaxis = spaceAvailableOnXaxis - (self.mGraph.paddingLeft + self.mGraph.paddingRight);
spaceAvailableOnXaxis = spaceAvailableOnXaxis*(xAxisMaxOriginal/xAxisMax);
barPlot.barWidth = spaceAvailableOnXaxis/(4*2);
barPlot.barOffset = 2;

Hope this helps someone out there :)

UPDATE

For all those who asked, I’m dumping my .h and .m files below.


//

//  TaggedVideoGraphViewController.h

//  SwimmerApp

//

//  Created by tilaye on 1/22/10.

//  Copyright 2010 __MyCompanyName__. All rights reserved.

//
 


#import <UIKit/UIKit.h>

#import "CorePlot-CocoaTouch.h"

#import <MessageUI/MessageUI.h>

#import "AnalysisInformation.h"

#import "CommonProtocols.h"

#import "GraphTheme.h"


typedef enum GraphType {GRAPH_STROKE_COUNT, GRAPH_STROKE_FREQUENCY, GRAPH_TIME} GraphType;


@interface TaggedVideoGraphViewController : UIViewController <CPPlotDataSource, MFMailComposeViewControllerDelegate, UITextFieldDelegate, UITextViewDelegate, AnalysisView> {

 CPXYGraph* mGraph;

 GraphType mGraphType;

 NSMutableArray* mGraphDataArray;

 NSString* mGraphTitle;

 UIBarButtonItem* mEmailButton;

 AnalysisInformation* mAnalysisInfo;

 IBOutlet UIView* mParentForGraphView;

 CPLayerHostingView* mGraphView;

 IBOutlet UILabel* mLabelBackgroundText;

}


@property (nonatomic, assign) GraphType mGraphType;

@property (nonatomic, retain) CPXYGraph* mGraph;

@property (nonatomic, retain) NSMutableArray* mGraphDataArray;

@property (nonatomic, retain) NSString* mGraphTitle;

@property (nonatomic, retain) UIBarButtonItem* mEmailButton;

@property (nonatomic, retain) AnalysisInformation* mAnalysisInfo;

@property (nonatomic, retain) CPLayerHostingView* mGraphView;

@property (nonatomic, retain) UIView* mParentForGraphView;

@property (nonatomic, retain) UILabel* mLabelBackgroundText;


- (NSMutableArray*) extractGraphArrayFromArray:(NSMutableArray*) array OfType:(GraphType)graphType;

- (double) findMax:(NSMutableArray*)array ForX:(Boolean)forX;

- (IBAction) emailButtonPressed;

- (void) renderGraph;

- (void) setTitle:(GraphType)GraphType;

- (CPLayerHostingView*) createGraphWithTheme:(id<GraphTheme>) theme;


@end


</pre>

//

//  TaggedVideoGraphViewController.m

//  SwimmerApp

//

//  Created by tilaye on 1/22/10.

//  Copyright 2010 __MyCompanyName__. All rights reserved.

//


#import "TaggedVideoGraphViewController.h"

#import "CorePlot-CocoaTouch.h"

#import "GraphDataPoint.h"

#import "TagStatisticsInformationBlock.h"

#import <MessageUI/MessageUI.h>

#import "VideoTag.h"

#import "EventInfo.h"

#import "AppConstants.h"

#import "GraphTheme.h"

#import "GraphThemeDark.h"

#import "GraphThemeLight.h"


@implementation TaggedVideoGraphViewController


@synthesize mGraphType;

@synthesize mGraph;

@synthesize mGraphDataArray;

@synthesize mGraphTitle;

@synthesize mEmailButton;

@synthesize mAnalysisInfo;

@synthesize mGraphView;

@synthesize mParentForGraphView;

@synthesize mLabelBackgroundText;


NSString* X_AXIS_TITLE = @"Mark";

double IPHONE_SCREEN_WIDTH = 320.0;


/*

 // The designated initializer.  Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {

 if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {

 // Custom initialization

 }

 return self;

}

*/


- (void) setAnalysisInformation:(AnalysisInformation*)AnalysisInfo {

 self.mAnalysisInfo = AnalysisInfo;

}

/*

// Override to allow orientations other than the default portrait orientation.

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {

 // Return YES for supported orientations

 return (interfaceOrientation == UIInterfaceOrientationPortrait);

}

*/


- (void)didReceiveMemoryWarning {

 // Releases the view if it doesn't have a superview.

 [super didReceiveMemoryWarning];


 // Release any cached data, images, etc that aren't in use.

}


- (void)viewDidUnload {

 // Release any retained subviews of the main view.

 // e.g. self.myOutlet = nil;

}


- (void)viewDidLoad {

 [super viewDidLoad];


 [self.view addSubview:self.mAnalysisInfo.options];


 [self setTitle:self.mGraphType];


 self.mGraphDataArray = [self extractGraphArrayFromArray:self.mAnalysisInfo.tagStatistics OfType:mGraphType];


 if ([self.mGraphDataArray count] <= 1) {

 self.mLabelBackgroundText.numberOfLines = 0;

 self.mLabelBackgroundText.lineBreakMode = UILineBreakModeWordWrap;

 self.mLabelBackgroundText.text = GRAPH_BACKGROUND_TEXT;

 self.mLabelBackgroundText.hidden = NO;

 self.mAnalysisInfo.parentView.navigationItem.rightBarButtonItem = nil;

 }else {

 self.mLabelBackgroundText.hidden = YES;

 mEmailButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCompose target:self action:@selector(emailButtonPressed)];

 self.mAnalysisInfo.parentView.navigationItem.rightBarButtonItem = mEmailButton;

 [self renderGraph];

 }

}


- (void) setTitle:(GraphType)GraphType {

 switch (GraphType) {

 case GRAPH_STROKE_COUNT:

 self.mGraphTitle = @"Strokes";

 break;

 case GRAPH_STROKE_FREQUENCY:

 self.mGraphTitle = @"Stroke Frequency";

 break;

 case GRAPH_TIME:

 self.mGraphTitle = @"Time";

 break;

 default:

 break;

 }

}


- (void) renderGraph {

 id<GraphTheme> theme = [[GraphThemeDark alloc] init];

 self.mGraphView = [self createGraphWithTheme:theme];

 [self.mParentForGraphView addSubview:mGraphView];

}


- (CPLayerHostingView*) createGraphWithTheme:(id<GraphTheme>) theme {

 //create graph

 CPXYGraph* graph;

 CPLayerHostingView* graphView;


 graph = [[CPXYGraph alloc] initWithFrame:self.mParentForGraphView.bounds];


 //    self.mGraphView = [[CPLayerHostingView alloc]initWithFrame:[UIScreen mainScreen].bounds];

 graphView = [[CPLayerHostingView alloc]initWithFrame:self.mParentForGraphView.bounds];

 CPLayerHostingView *hostingView = (CPLayerHostingView *)graphView;

 hostingView.hostedLayer = graph;

 graph.paddingLeft = 55.0;

 graph.paddingTop = 20.0;

 graph.paddingRight = 15.0;

 graph.paddingBottom = 50.0;


 //set plot space

 double xAxisMaxOriginal = [self findMax:mGraphDataArray ForX:YES];

 double xAxisMax = [self findMax:mGraphDataArray ForX:YES];

 double yAxisMax = [self findMax:mGraphDataArray ForX:NO];

 //leave a small gap at the end so graph does go till the end

 xAxisMax = xAxisMax + 0.2*xAxisMax;

 yAxisMax = yAxisMax + 0.2*yAxisMax;


 CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace;

 plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)

 length:CPDecimalFromFloat(xAxisMax)];

 plotSpace.yRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)

 length:CPDecimalFromFloat(yAxisMax)];


 //set line style

 CPLineStyle *lineStyle = [CPLineStyle lineStyle];

 lineStyle.lineColor = [CPColor blackColor];

 lineStyle.lineWidth = 2.0f;


 //setup x-axis


 CPXYAxisSet *axisSet = (CPXYAxisSet *)graph.axisSet;

 axisSet.xAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:@"1"] decimalValue];

 axisSet.xAxis.minorTicksPerInterval = 0;

 axisSet.xAxis.majorTickLineStyle = lineStyle;

 axisSet.xAxis.minorTickLineStyle = lineStyle;

 axisSet.xAxis.axisLineStyle = lineStyle;

 axisSet.xAxis.minorTickLength = 5.0f;

 axisSet.xAxis.majorTickLength = 7.0f;

 axisSet.xAxis.labelOffset = 3.0f;

 axisSet.xAxis.title = X_AXIS_TITLE;

 NSString *xAxisTitleLocation = [NSString stringWithFormat:@"%f", (xAxisMax/2)];

 axisSet.xAxis.titleLocation = [[NSDecimalNumber decimalNumberWithString:xAxisTitleLocation] decimalValue];;

 NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease];

 [formatter setNumberStyle:NSNumberFormatterDecimalStyle];

 axisSet.xAxis.labelFormatter = formatter;


 //setup y-axis

 //    axisSet.yAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:@"5"] decimalValue];

 NSString *yAxisMajorIntervalLength = [NSString stringWithFormat:@"%f", (yAxisMax/5)];

 axisSet.yAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:yAxisMajorIntervalLength] decimalValue];

 axisSet.yAxis.minorTicksPerInterval = 4;

 axisSet.yAxis.majorTickLineStyle = lineStyle;

 axisSet.yAxis.minorTickLineStyle = lineStyle;

 axisSet.yAxis.axisLineStyle = lineStyle;

 axisSet.yAxis.minorTickLength = 5.0f;

 axisSet.yAxis.majorTickLength = 7.0f;

 axisSet.yAxis.labelOffset = 3.0f;

 axisSet.yAxis.title = mGraphTitle;

 NSString *yAxisTitleLocation = [NSString stringWithFormat:@"%f", (yAxisMax/2)];

 axisSet.yAxis.titleLocation = [[NSDecimalNumber decimalNumberWithString:yAxisTitleLocation] decimalValue];


 //create bar plot

 CPBarPlot* barPlot = [[[CPBarPlot alloc] initWithFrame:graph.defaultPlotSpace.graph.bounds] autorelease];

 barPlot.identifier = @"mGraphTitle";

 barPlot.dataSource = self;


 //set bar width

 double spaceAvailableOnXaxis = IPHONE_SCREEN_WIDTH;

 //take out the padding on left and right

 spaceAvailableOnXaxis = spaceAvailableOnXaxis - (graph.paddingLeft + graph.paddingRight);

 //take out the small gap at the end of the xaxis.

 spaceAvailableOnXaxis = spaceAvailableOnXaxis*(xAxisMaxOriginal/xAxisMax);

 //the width of a bar is equal to half of the distance between successive marks

 //in other words, there will be twice as many bar slots as there are marks, and we use

 //every other bar slot

 double barWidthRatio = spaceAvailableOnXaxis/([mGraphDataArray count]*2);

 barPlot.barWidth = barWidthRatio;

 //drawing a bar should start from mark 1, not 0. Between 0 and one there are two bar slots so skip those.

 barPlot.barOffset = 2;


 //set bar fill

 CPFill* cpFill = [[CPFill alloc] initWithColor:[theme barFillColor]];

 barPlot.fill = cpFill;

 [cpFill release];


 //set bar label text size and color - not being used at the moment because there aren't any bar labels yet

 CPTextStyle* textStyle = [CPTextStyle textStyle];

 barPlot.barLabelTextStyle = textStyle;

 textStyle.fontSize = 20.0;


 //apply theme

 //axes label color

 axisSet.xAxis.labelTextStyle.color = [theme axesFontColor];

 axisSet.yAxis.labelTextStyle.color = [theme axesFontColor];

 axisSet.xAxis.titleTextStyle.color = [theme axesFontColor];

 axisSet.yAxis.titleTextStyle.color = [theme axesFontColor];

 //axes line color

 axisSet.xAxis.axisLineStyle.lineColor = [theme axesLineColor];

 axisSet.yAxis.axisLineStyle.lineColor = [theme axesLineColor];

 //axes tick mark color

 axisSet.xAxis.minorTickLineStyle.lineColor= [theme tickMarkColor];

 axisSet.xAxis.majorTickLineStyle.lineColor= [theme tickMarkColor];

 axisSet.yAxis.minorTickLineStyle.lineColor= [theme tickMarkColor];

 axisSet.yAxis.majorTickLineStyle.lineColor= [theme tickMarkColor];

 //bar plot border color

 //barPlot.borderColor = [theme barBorderColor];


 //set view background

 graphView.backgroundColor = [theme backgroundColor];


 //add graph to view

 [graph addPlot:barPlot];


 return [graphView autorelease];

}


- (void) renderGraphold {

 //create graph

 self.mGraph = [[CPXYGraph alloc] initWithFrame:self.mParentForGraphView.bounds];


//    self.mGraphView = [[CPLayerHostingView alloc]initWithFrame:[UIScreen mainScreen].bounds];

 self.mGraphView = [[CPLayerHostingView alloc]initWithFrame:self.mParentForGraphView.bounds];

 CPLayerHostingView *hostingView = (CPLayerHostingView *)self.mGraphView;

 hostingView.hostedLayer = self.mGraph;

 self.mGraph.paddingLeft = 55.0;

 self.mGraph.paddingTop = 20.0;

 self.mGraph.paddingRight = 15.0;

 self.mGraph.paddingBottom = 50.0;


 //set plot space

 double xAxisMaxOriginal = [self findMax:mGraphDataArray ForX:YES];

 double xAxisMax = [self findMax:mGraphDataArray ForX:YES];

 double yAxisMax = [self findMax:mGraphDataArray ForX:NO];

 //leave a small gap at the end so graph does go till the end

 xAxisMax = xAxisMax + 0.2*xAxisMax;

 yAxisMax = yAxisMax + 0.2*yAxisMax;


 CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)self.mGraph.defaultPlotSpace;

 plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)

 length:CPDecimalFromFloat(xAxisMax)];

 plotSpace.yRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0)

 length:CPDecimalFromFloat(yAxisMax)];


 //set line style

 CPLineStyle *lineStyle = [CPLineStyle lineStyle];

 lineStyle.lineColor = [CPColor blackColor];

 lineStyle.lineWidth = 2.0f;


 //setup x-axis


 CPXYAxisSet *axisSet = (CPXYAxisSet *)self.mGraph.axisSet;

 axisSet.xAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:@"1"] decimalValue];

 axisSet.xAxis.minorTicksPerInterval = 0;

 axisSet.xAxis.majorTickLineStyle = lineStyle;

 axisSet.xAxis.minorTickLineStyle = lineStyle;

 axisSet.xAxis.axisLineStyle = lineStyle;

 axisSet.xAxis.minorTickLength = 5.0f;

 axisSet.xAxis.majorTickLength = 7.0f;

 axisSet.xAxis.labelOffset = 3.0f;

 axisSet.xAxis.title = X_AXIS_TITLE;

 NSString *xAxisTitleLocation = [NSString stringWithFormat:@"%f", (xAxisMax/2)];

 axisSet.xAxis.titleLocation = [[NSDecimalNumber decimalNumberWithString:xAxisTitleLocation] decimalValue];;

 NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease];

 [formatter setNumberStyle:NSNumberFormatterDecimalStyle];

 axisSet.xAxis.labelFormatter = formatter;


 //setup y-axis

 //    axisSet.yAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:@"5"] decimalValue];

 NSString *yAxisMajorIntervalLength = [NSString stringWithFormat:@"%f", (yAxisMax/5)];

 axisSet.yAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:yAxisMajorIntervalLength] decimalValue];

 axisSet.yAxis.minorTicksPerInterval = 4;

 axisSet.yAxis.majorTickLineStyle = lineStyle;

 axisSet.yAxis.minorTickLineStyle = lineStyle;

 axisSet.yAxis.axisLineStyle = lineStyle;

 axisSet.yAxis.minorTickLength = 5.0f;

 axisSet.yAxis.majorTickLength = 7.0f;

 axisSet.yAxis.labelOffset = 3.0f;

 axisSet.yAxis.title = mGraphTitle;

 NSString *yAxisTitleLocation = [NSString stringWithFormat:@"%f", (yAxisMax/2)];

 axisSet.yAxis.titleLocation = [[NSDecimalNumber decimalNumberWithString:yAxisTitleLocation] decimalValue];


 /*

 //create line plot

 CPScatterPlot *linePlot = [[[CPScatterPlot alloc]

 initWithFrame:self.mGraph.defaultPlotSpace.graph.bounds] autorelease];

 linePlot.identifier = mGraphTitle;

 linePlot.dataLineStyle.lineWidth = 1.0f;

 linePlot.dataLineStyle.lineColor = [CPColor redColor];

 linePlot.dataSource = self;

 [self.mGraph addPlot:linePlot];


 //create markers

 CPPlotSymbol *greenCirclePlotSymbol = [CPPlotSymbol ellipsePlotSymbol];

 greenCirclePlotSymbol.fill = [CPFill fillWithColor:[CPColor greenColor]];

 greenCirclePlotSymbol.size = CGSizeMake(2.0, 2.0);

 [linePlot setPlotSymbol:greenCirclePlotSymbol];

 */


 //create bar plot

 CPBarPlot* barPlot = [[[CPBarPlot alloc] initWithFrame:self.mGraph.defaultPlotSpace.graph.bounds] autorelease];

 barPlot.identifier = @"mGraphTitle";

 barPlot.dataSource = self;


 //set bar width

 double spaceAvailableOnXaxis = IPHONE_SCREEN_WIDTH;

 //take out the padding on left and right

 spaceAvailableOnXaxis = spaceAvailableOnXaxis - (self.mGraph.paddingLeft + self.mGraph.paddingRight);

 //take out the small gap at the end of the xaxis.

 spaceAvailableOnXaxis = spaceAvailableOnXaxis*(xAxisMaxOriginal/xAxisMax);

 //the width of a bar is equal to half of the distance between successive marks

 //in other words, there will be twice as many bar slots as there are marks, and we use

 //every other bar slot

 double barWidthRatio = spaceAvailableOnXaxis/([mGraphDataArray count]*2);

 barPlot.barWidth = barWidthRatio;

 //drawing a bar should start from mark 1, not 0. Between 0 and one there are two bar slots so skip those.

 barPlot.barOffset = 2;


 //set bar fill

 CPFill* cpFill = [[CPFill alloc] initWithColor:[CPColor lightGrayColor]];

 barPlot.fill = cpFill;


 //set bar text

 CPTextStyle* textStyle = [CPTextStyle textStyle];

 barPlot.barLabelTextStyle = textStyle;

 textStyle.fontSize = 20.0;


 [self.mGraph addPlot:barPlot];

//    [self.view addSubview:self.mGraphView];

//    [self.mAnalysisInfo.options addSubview:mGraphView];

 [self.mParentForGraphView addSubview:mGraphView];

}


-(NSUInteger)numberOfRecordsForPlot:(CPPlot *)plot {

 return [mGraphDataArray count];

}


-(NSNumber *)numberForPlot:(CPPlot *)plot field:(NSUInteger)fieldEnum

 recordIndex:(NSUInteger)index

{

 NSLog(@"TaggedVideoGraphViewController.numberForPlot:field:recordIndex");

 GraphDataPoint* dataPoint = (GraphDataPoint*)[mGraphDataArray objectAtIndex:index];

 switch (fieldEnum) {

 case CPScatterPlotFieldX:

 return [NSNumber numberWithDouble:dataPoint.mX];

 break;

 case CPScatterPlotFieldY:

 return [NSNumber numberWithDouble:dataPoint.mY];

 break;

 default:

 NSLog(@"TaggedVideoGraphViewController.numberForPlot:field:recordIndex invalidd fieldEnum, %d", fieldEnum);

 return [NSNumber numberWithInteger:-1];

 break;

 }

}


- (NSMutableArray*) extractGraphArrayFromArray:(NSMutableArray*) array OfType:(GraphType)graphType {

 NSMutableArray* graphArray = [[NSMutableArray alloc] init];

 for (int i=0; i<[self.mAnalysisInfo.tagStatistics count]; i++) {

 GraphDataPoint* dataPoint = (GraphDataPoint*)[[GraphDataPoint alloc] init];

 TagStatisticsInformationBlock* infoBlock = (TagStatisticsInformationBlock*)[self.mAnalysisInfo.tagStatistics objectAtIndex:i];

 dataPoint.mX = infoBlock.mMark;

 switch (graphType) {

 case GRAPH_STROKE_COUNT:

 dataPoint.mY = infoBlock.mStrokeCount;

 break;

 case GRAPH_STROKE_FREQUENCY:

 dataPoint.mY = infoBlock.mStrokeFrequency;

 break;

 case GRAPH_TIME:

 dataPoint.mY = infoBlock.mTime;

 break;

 default:

 break;

 }

 [graphArray addObject:dataPoint];

 [dataPoint release];

 }

 return graphArray;

}


//if ForX==YES then max value for X will be searched, if not then max value for Y will be searched

- (double) findMax:(NSMutableArray*)array ForX:(Boolean)forX{

 double max = -1.0;

 for (int i=0; i<[array count]; i++) {

 GraphDataPoint* dataPoint = (GraphDataPoint*)[array objectAtIndex:i];

 if (forX == YES) {

 if (dataPoint.mX > max) {

 max = dataPoint.mX;

 }

 }else {

 if (dataPoint.mY > max) {

 max = dataPoint.mY;

 }

 }

 }

 return max;

}


- (IBAction) emailButtonPressed {

 MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];

 picker.mailComposeDelegate = self;


 // Email subject

 NSString* emailSubject = mGraphTitle;

 emailSubject = [emailSubject stringByAppendingString:@" Graph: "];

 emailSubject = [emailSubject stringByAppendingString:self.mAnalysisInfo.videoTag.TagDescription];

 emailSubject = [emailSubject stringByAppendingString:@" "];

 emailSubject = [emailSubject stringByAppendingString:[((EventInfo*)self.mAnalysisInfo.videoTag.EventInfo) EventDescriptionLong]];

 [picker setSubject:emailSubject];


 // Email recipients

 //NSArray *toRecipients = [NSArray arrayWithObject:@"tilaye@gmail.com"];

 //[picker setToRecipients:toRecipients];


 // Email Attachment

 /*

 UIGraphicsBeginImageContext(self.mGraphView.frame.size);

 // Do translations (image will be inverted otherwise)

 CGContextRef ctx = UIGraphicsGetCurrentContext();

 CGContextTranslateCTM(ctx, 0.0, self.mGraphView.frame.size.height);

 CGContextScaleCTM(ctx, 1.0, -1.0);

 [self.mGraphView.layer renderInContext:UIGraphicsGetCurrentContext()];

 UIImage* viewImage = UIGraphicsGetImageFromCurrentImageContext();

 UIGraphicsEndImageContext();

 */


 id<GraphTheme> theme = [[GraphThemeLight alloc] init];

 CPLayerHostingView* graphView = [self createGraphWithTheme:theme];

 UIGraphicsBeginImageContext(graphView.frame.size);

 // Do translations (image will be inverted otherwise)

 CGContextRef ctx = UIGraphicsGetCurrentContext();

 CGContextTranslateCTM(ctx, 0.0, graphView.frame.size.height);

 CGContextScaleCTM(ctx, 1.0, -1.0);

 [graphView.layer renderInContext:UIGraphicsGetCurrentContext()];

 UIImage* viewImage = UIGraphicsGetImageFromCurrentImageContext();

 UIGraphicsEndImageContext();


 NSData* myData = UIImagePNGRepresentation(viewImage);

 [picker addAttachmentData:myData mimeType:@"image/png" fileName:@"Filename"];


 // Email text

 // name

 NSString *emailBody = @"Name: ";

 emailBody = [emailBody stringByAppendingString:self.mAnalysisInfo.videoTag.TagDescription];

 emailBody = [emailBody stringByAppendingString:@"\n"];

 //style

 emailBody = [emailBody stringByAppendingString:@"Style: "];

 emailBody = [emailBody stringByAppendingString:[((EventInfo*)self.mAnalysisInfo.videoTag.EventInfo) EventDescriptionLong]];

 emailBody = [emailBody stringByAppendingString:@"\n"];

 //time

 NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];

 [dateFormatter setDateFormat:@"dd MMMMM yyyy hh:mm:ss aaa"];

 emailBody = [emailBody stringByAppendingString:@"Time: "];

 NSString* formattedDate = [dateFormatter stringFromDate:(NSDate*)self.mAnalysisInfo.videoTag.TagDate];

 emailBody = [emailBody stringByAppendingString:formattedDate];

 [picker setMessageBody:emailBody isHTML:NO];

 [dateFormatter release];


 [self.mAnalysisInfo.parentView.navigationController  presentModalViewController:picker animated:YES];

 [picker release];


}


// Dismisses the email composition interface when users tap Cancel or Send. Proceeds to update the message field with the result of the operation.

- (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error

{

 NSString* emailStatus;

 // Notifies users about errors associated with the interface

 switch (result)

 {

 case MFMailComposeResultCancelled:

 emailStatus = @"Result: canceled";

 break;

 case MFMailComposeResultSaved:

 emailStatus = @"Result: saved";

 break;

 case MFMailComposeResultSent:

 emailStatus = @"Result: sent";

 break;

 case MFMailComposeResultFailed:

 emailStatus = @"Result: failed";

 break;

 default:

 emailStatus = @"Result: not sent";

 break;

 }

 [self.mAnalysisInfo.parentView.navigationController dismissModalViewControllerAnimated:YES];

}


- (BOOL)textFieldShouldReturn:(UITextField *)textField {

 [textField resignFirstResponder];

 return YES;

}


- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text

{

 NSLog(@"TaggedVideoGraphViewController.textView:shouldChangeTextInRange:replacementText:");

 // Any new character added is passed in as the "text" parameter

 if (1) {

 // Be sure to test for equality using the "isEqualToString" message

 [textView resignFirstResponder];


 // Return FALSE so that the final '\n' character doesn't get added

 return FALSE;

 }

 // For any other character return TRUE so that the text gets added to the view

 return TRUE;

}


- (void) viewWillDisappear:(BOOL)animated {

 [super viewWillDisappear:animated];

 [mGraphView removeFromSuperview];

}


- (void)dealloc {

 [mGraph release];

 [mGraphDataArray release];

 [mGraphTitle release];

 [mEmailButton release];

 [mAnalysisInfo release];

 [mGraphView release];

 [mParentForGraphView release];

 [mLabelBackgroundText release];

 [super dealloc];

}


@end

<pre>

 

Update 2 Theme files

//
//  GraphThemeDark.h
//  SwimmerApp
//
//  Created by tilaye on 2/28/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "GraphTheme.h"

@interface GraphThemeDark : NSObject <GraphTheme> {

}

@end

//
//  GraphThemeDark.m
//  SwimmerApp
//
//  Created by tilaye on 2/28/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "GraphThemeDark.h"


@implementation GraphThemeDark

- (UIColor*) backgroundColor {
 return [UIColor blackColor];
}

- (CPColor*) axesFontColor {
 return [CPColor whiteColor];
}

- (CPColor*) barFillColor {
 return [CPColor darkGrayColor];
}

- (CPColor*) axesLineColor {
 return [CPColor darkGrayColor];
}

- (CPColor*) tickMarkColor {
 return [CPColor darkGrayColor];
}

- (CPColor*) barBorderColor {
 return [CPColor darkGrayColor];
}

@end

//
//  GraphThemeLight.h
//  SwimmerApp
//
//  Created by tilaye on 2/28/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "GraphTheme.h"

@interface GraphThemeLight : NSObject <GraphTheme> {

}

@end

//
//  GraphThemeLight.m
//  SwimmerApp
//
//  Created by tilaye on 2/28/10.
//  Copyright 2010 __MyCompanyName__. All rights reserved.
//

#import "GraphThemeLight.h"
#import "GraphTheme.h"


@implementation GraphThemeLight

- (UIColor*) backgroundColor {
 return [UIColor whiteColor];
}

- (CPColor*) axesFontColor {
 return [CPColor blackColor];
}

- (CPColor*) barFillColor {
 return [CPColor lightGrayColor];
}

- (CPColor*) axesLineColor {
 return [CPColor blackColor];
}

- (CPColor*) tickMarkColor {
 return [CPColor blackColor];
}

- (CPColor*) barBorderColor {
 return [CPColor blackColor];
}

@end



<pre>

Generate Doxygen documentation for core-plot

This post is about how to generate the documentation for core-plot, a graph plotting framework for Cocoa. As the project’s documentation page explains, they use Doxygen to document their code. I followed the steps below to generate the docs.

1. Download and install Graphviz. This is required by Doxygen.

2. Download and install Doxygen.

3. Open Doxygen (type “Doxygen” in spotlight).

4.1 First step is to specify the working directory for Doxygen. I created an empty directory “doxygen-working-dir” for this purpose (I later saved my doxygen settings in this directory). I then gave the project name “core-plot”, and set the source directory box to “core-plot/framework/Source”. I created an empty directory for the destination too.

4.2 I then went to “Run” tab and pressed “Run doxygen”. That generated HTML and Latex output.

EDIT

You can download the docs I generated here. The license file that comes with it is bundled in the zip file. Remember that you’re best generating the docs yourself from the latest source code though.

Icon stuck on top of screen in Windows

Have you ever had an icon stuck on your screen? Some icon you have, probably on your desktop, simply sits there on top of all windows you open. It’s not clickable. It occasionally blocks your view and when it doesn’t, you somehow feel it’s presence all the time. Very annoying. This is some redraw/refresh problem of course. I have tried pressing CTRL+ALT+DELETE in hope that the icon goes away, playing a video with Windows Media Player, turning on my screen saver,… nothing worked. But today I found a solution! The icon went away when I clicked on a random icon from my Quick Launch and started to drag it. Don’t waste your time logging out and back in.

EXC_BAD_ACCESS on NSError

The following code kept giving me EXC_BAD_ACCESS,


NSError* error;
 NSMutableArray* configurationArray = [[mManagedObjectContext executeFetchRequest:request error:&error] mutableCopy];
 if (error != nil) {
 NSLog(@"Error: %@", error);
 }

Apple has a list of suggestions on what might be causing this. However my problem was different, which was that I wasn’t initializing error pointer as such,


NSError* error = nil;

I miss Java :(

Showing a modal view with a navigation bar

This took me about an hour to figure out so I’m blogging it. I was interested to show a modal view with a navigation bar at the top. I wanted the navigation bar so as to put a descriptive title for the modal view. I read one solution here which is to use a label to show the title. But I kind of like the look of navigation bars at the top. Besides, the HIG from Apple shows a modal view with a navigation bar so I think showing the title this way is an accepted approach.

I added a navigation bar in the Xib file but the navigation bar was not showing in the modal view. I fiddled with some other options with no luck. Thanks to this discussion which showed me the right way.

What I did was change my original code,

DatePickerViewController *datePickerViewController = [[DatePickerViewController alloc] initWithNibName:@"DatePickerViewController" bundle:nil];
[self presentModalViewController:datePickerViewController animated:YES];

to this,

DatePickerViewController *datePickerViewController = [[DatePickerViewController alloc] initWithNibName:@"DatePickerViewController" bundle:nil];
 UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:datePickerViewController];
[self presentModalViewController:navigationController animated:YES];

and my modal view has,

- (void)viewDidLoad {
 [super viewDidLoad];
 self.title = @"Set date and time";
}

I still don’t understand why adding the navigation bar in the Xib file did not work. Maybe there is something more fundamental I am missing. I’ve spent too much time on this already though, time to move on!

NSMutableArray to NSData and NSData to NSMutableArray

Follow these steps :)

Your objects should adopt NSCoding

First thing is elements of your array should adopt the NSCoding protocol. Some classes such as NSString and NSDate already adopt this protocol. So you will be fine if your array is full of objects that already adopt NSCoding. However, you must do a tiny bit of work if you have a custom class of your own or some other class that is not NSCoding friendly. There is a nice tutorial about this. The gist of it is if the elements of your array are objects of type MyClass then adopt NSCoding in the header file,

#import <Foundation/NSCoder.h>
@interface MyClass : NSObject <NSCoding> {…}

In my situation MyClass has two instance methods, one is of type NSDate and the other is integer. I want both of these to be persisted. So in MyClass.m I implement the two methods required by NSCoding as such,

-(void) encodeWithCoder: (NSCoder *) encoder
{
  [encoder encodeObject: myDateVarialbe];
  [encoder encodeObject: [NSNumber numberWithInteger:myIntegerVariable]];
}

-(id) initWithCoder: (NSCoder *) decoder
{
  myDateVariable = [[decoder decodeObject] retain];
  myIntegerVariable = [[[decoder decodeObject] retain] intValue];
  return self;
}

As you might have guessed, the first method gets called while encoding and the second while decoding. The array is now ready to be encoded.

NSMutableArray to NSData

NSData* myData = [NSKeyedArchiver archivedDataWithRootObject:myMutableArray];

NSData to NSMutableArray

NSMutableArray* myMutableArray = [NSKeyedUnarchiver unarchiveObjectWithData:myData];

Here I’m using NSKeyedArchiver and its counterpart NSKeyedUnarchiver. There are other archivers too. I chose NSKeyedArchiver because the documentation says it supports backward compatibility better.