Current time and date in Ethiopia

Checkout time.ertale.com for the current Ethiopian date and time, the way Ethiopians count it.

Few points,

  • Big thanks to ethiopic.org for the Gregorian to Ethiopia conversion algorithm.
  • Date and time rendered as image to avoid font problems.
  • I assumed it’s correct to split the day as
    • Kelelitu 6:00 se’at 11:59 se’at
    • Ketewatu 12:00 se’at – 5:59 se’at
    • Kese’at 6:00 se’at 11:59 se’at
    • Kemishitu 12:00 se’at – 5:59 se’at
  • Wishlist:
    • Upcoming holidays at the bottom.

Creating UITableViewCells with “unread” blue dots

There is no straightforward built in functionality for this. What you can do is create the blue dots (or whatever image you like) and set the cell.imageView.image property. I asked on stackoverflow about a problem I faced which is the images weren’t looking nice when a cell is selected. Here is how it looked (first image for “unread” cells and  second for “read” cells)

What is needed is to set a transparent background for the images. Thanks to this post I found a way of masking the images. I’m using the function posted on that website with the following images. Blue dot for “unread” cells, white dot for “read” mails, and the mask image. Feel free to use these images but they are not the typical shiny iphone images. If you know how to make one of those please comment.

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.

Coreplot on iPhone

I followed Using Core Plot in an iPhone Application to generate a graph. I got some errors though. I originally wanted to comment on the tutorial page but wasn’t allowed to. The errors,

/Users/tilaye/Desktop/apps/CorePlotTest/Classes/CorePlotTestViewController.m:106: error: incompatible type for argument 1 of 'setMajorIntervalLength:'
/Users/tilaye/Desktop/apps/CorePlotTest/Classes/CorePlotTestViewController.m:113: error: request for member 'axisLabelOffset' in something not a structure or union
/Users/tilaye/Desktop/apps/CorePlotTest/Classes/CorePlotTestViewController.m:115: error: incompatible type for argument 1 of 'setMajorIntervalLength:'
/Users/tilaye/Desktop/apps/CorePlotTest/Classes/CorePlotTestViewController.m:122: error: request for member 'axisLabelOffset' in something not a structure or union
/Users/tilaye/Desktop/apps/CorePlotTest/Classes/CorePlotTestViewController.m:125: error: request for member 'bounds' in something not a structure or union
/Users/tilaye/Desktop/apps/CorePlotTest/Classes/CorePlotTestViewController.m:135: error: request for member 'defaultPlotSymbol' in something not a structure or union
/Users/tilaye/Desktop/apps/CorePlotTest/Classes/CorePlotTestViewController.m:138: error: request for member 'bounds' in something not a structure or union
replaced axisLabelOffset with labelOffset,

I did the following to work around the issue,

replaced axisLabelOffset with labelOffset

replaced graph.defaultPlotSpace.bounds with graph.defaultPlotSpace.graph.bounds

replaced [NSDecimalNumber decimalNumberWithString:@"5"] with [[NSDecimalNumber decimalNumberWithString:@"5"] decimalValue]

My final working viewDidLoad method was then,

- (void)viewDidLoad {
  [super viewDidLoad];

  graph = [[CPXYGraph alloc] initWithFrame: self.view.bounds];
  self.view = [[CPLayerHostingView alloc]initWithFrame:[UIScreen mainScreen].bounds];
  CPLayerHostingView *hostingView = (CPLayerHostingView *)self.view;
  hostingView.hostedLayer = graph;
  graph.paddingLeft = 20.0;
  graph.paddingTop = 20.0;
  graph.paddingRight = 20.0;
  graph.paddingBottom = 20.0;

  CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace;
  plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-6)
  length:CPDecimalFromFloat(12)];
  plotSpace.yRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-5)
  length:CPDecimalFromFloat(30)];

  CPLineStyle *lineStyle = [CPLineStyle lineStyle];
  lineStyle.lineColor = [CPColor blackColor];
  lineStyle.lineWidth = 2.0f;

  CPXYAxisSet *axisSet = (CPXYAxisSet *)graph.axisSet;
  axisSet.xAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:@"5"] decimalValue];
  axisSet.xAxis.minorTicksPerInterval = 4;
  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.yAxis.majorIntervalLength = [[NSDecimalNumber decimalNumberWithString:@"5"] 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;

  CPScatterPlot *xSquaredPlot = [[[CPScatterPlot alloc]
  initWithFrame:graph.defaultPlotSpace.graph.bounds] autorelease];
  xSquaredPlot.identifier = @"X Squared Plot";
  xSquaredPlot.dataLineStyle.lineWidth = 1.0f;
  xSquaredPlot.dataLineStyle.lineColor = [CPColor redColor];
  xSquaredPlot.dataSource = self;
  [graph addPlot:xSquaredPlot];

  CPPlotSymbol *greenCirclePlotSymbol = [CPPlotSymbol ellipsePlotSymbol];
  greenCirclePlotSymbol.fill = [CPFill fillWithColor:[CPColor greenColor]];
  greenCirclePlotSymbol.size = CGSizeMake(2.0, 2.0);

  [xSquaredPlot setPlotSymbol:greenCirclePlotSymbol];

  CPScatterPlot *xInversePlot = [[[CPScatterPlot alloc]
  initWithFrame:graph.defaultPlotSpace.graph.bounds] autorelease];
  xInversePlot.identifier = @"X Inverse Plot";
  xInversePlot.dataLineStyle.lineWidth = 1.0f;
  xInversePlot.dataLineStyle.lineColor = [CPColor blueColor];
  xInversePlot.dataSource = self;
  [graph addPlot:xInversePlot];
}

I also got

/Users/tilaye/Desktop/apps/TestApp/Classes/TaggedVideoGraphViewController.m:135: warning: method definition for '-numberOfRecordsForPlot:' not found

and changed my function signature from

-(NSUInteger)numberOfRecords {

to

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

Recover iphone contacts and SMS (text) from backup

I just broke my iPhone. I need to wait a while before I could get a replacement. In the meanwhile, I needed to recover my contacts (address book) from a backup file. Thanks to this discussion where I got most of the info I needed. There were a few turns though. So I’ll write the steps if it helps somebody.

I used: Windows XP, iTunes 8.2, Firefox.

1. Back backup

I needed to use a tool to recover my data. I first backed up my backup files because I wasn’t sure if I could trust the tool. So I simply made a copy of the folder C:\Documents and Settings\MyUserName\Application Data\Apple Computer\MobileSync\Backup in another folder. I finally realized that I didn’t need this step because everything worked ok. But if you want to be comfortable then go for it. It took my machine about 4 minutes to copy the whole folder.

2. Extract address book

To extract the address book from the backup file, I used the tool Backup Extractor for iPhone (see update at the end of this post for a more recentl URL). I downloaded the free version which turned out to satisfy my needs allright. I run the program and I got a list of backup files to restore. Some were iPhone files and the rest were iPod files. I chose the most recent iPhone backup. The second step of the restore wizard brought up two options. I had to choose the second option (manual). The first one is not available in the free version of the software anyway. On the third step, I navigated to Library->AddressBook and checked AddressBook.sqlitedb. At the last step I chose to put the restored file in C:\restored. When the process finished I got the file C:\restored\AddressBook.sqlitedb. I then had to rename the extension of this file from .sqlitedb to .sqlite. This will become important shortly.

3. Open AddressBook database

I opened firefox and installed the SQLite Manager add-on for Firefox, restarted firefox. I then went to Tools->SQLite Manager. On SQLite Manager window I clicked the open icon and selected C:\restored\AddressBook.sqlite.

4. Retrieve contacts from database

The discussion I referred to earlier gives a nice query to select contacts from the database,

select ABPerson.first,ABPerson.last, ABMultiValue.value from ABPerson,ABMultiValue where ABMultiValue.record_id=ABPerson.ROWID

I went to “Execute SQL” tab in SQLite Manager window in Firefox, pasted the above query and run it. That gave all my contacts. Some of the numbers were missing first and last names so I couldn’t tell who they belonged to. The trick was to change the query to select the ABPerson.prefix column too,

select ABPerson.prefix, ABPerson.first,ABPerson.last, ABMultiValue.value from ABPerson,ABMultiValue where ABMultiValue.record_id=ABPerson.ROWID

Now I need to call my insurance…

EDIT

I just recovered my SMS as well. The process is similar except in step 2 extract Library->SMS->check sms.db and rename sms.db to sms.sqlite. In step 4 use the query,

SELECT rowid, address, date, text FROM message ORDER BY date

UPDATE

iPhone Backup Extractor has moved to a new address now. Thanks to Matt Collins for the update.