Using Microsoft Kinect for mouse movement and more

I recently hacked Microsoft Kinect to control mouse movement. I experimented with other actions too. I’ll talk about some challenges and my work around.

First off, programmatically moving the mouse and pressing keyboard keys is easy. The real work had been in recognizing gestures and reacting to them in a nice way.

Kinect. Getting started with the kinect was fun. The SDK provides “Skeleton Tracking” by giving the location of 20 joints in the body. Some of these are left hand, right knee, and head position. So technically speaking you can write code to move the mouse by waving your right foot in the air. Ignoring the details, each joint can be considered as a 3D point, the position of which is updated several times per second.

Activating and deactivating gestures 1. My first task was to have a “Movement of left hand to move mouse” gesture. When to move the mouse and when not? It’d be annoying if any movement of my left hand caused the mouse to move. I decided I’d enable the gesture only if the user is stretching their left arm forward. I compared the “z” component, or “depth” in kinect SDK language, of the left hand joint with the “z” component of the shoulder center joint. In the figure below, the gesture becomes active if the left hand is on the right side of the dotted line.

Scaling values. Once the gesture is active, second step was to react to left hand movements. Scale is important here. I took a hacky and quick approach, which was to scale the values I got from kinect to [0, 1] and scale them back again using a fixed constant. I experimented with the constant until I was able to to move the mouse from one end of the screen to the other end without having to walk to the side or stretching myself too hard. A neat approach here would be to determine the scale automatically. I have seen people following a “calibration” step where at the beginning the user makes special gestures to decide opposite corners of the rectangle they want to correspond to the screen. Although this works too, I think it can be improved by automatically determining a rectangle that is within a reasonable reach of the user’s hands.

Activating and deactivating gestures 2. I also noticed that if I started moving my hand from the “stretched to front” position to the “stretched to side” position, say to move the mouse from the right end of the screen to the left, the mouse would stop moving at some point because my hand was no longer in the active area. Second improvement was then to separate the activation and deactivation distance and put a bit more gap between them. The gesture gets activated if my hand is to the right of the activation line. Once activated, moving my hand to the left of the activation line does not deactivate the gesture. The gesture remains active until my hand moves to the left of the deactivation line.

One noticeable problem now is that I’m not able to move the mouse to a specific location and leave it there. It wouldn’t work to move the mouse to where I want and then to simply drop my hand. The drop itself will cause a downward movement of the mouse. To minimize this side effect I tried pulling my hand close to my chest. This would change my hand’s “z” value while keeping its xy position as it is. But there is almost always a minor movement during this process. Any suggestions let me know.

Smooth mouse movements. The final issue was how to move the mouse smoothly. The mouse would jump here and there as if I was shaking my hand. This would happen even when I held my hand still. To overcome the noise I first tried averaging the current position and the one just before it. But this wasn’t enough so I kept increasing the points I was averaging across until I got a smooth movement. I finally settled with 30 points. As I mentioned earlier, joint positions are updated several times per second. The sensor itself fires 30 frames per second. The SDK probably fires joint updates less frequently, but there is still plenty of information within a short time. So 30 consecutive points do not span as large amount of time as you might first think. I keep a fixed size queue of length 30, when the sensor gives me a new location I pop in the new one, pop out the oldest one, compute the average and move the mouse. Due to the averaging process, there is some lag between my gesture and its effect on the mouse position. When I stop moving my hand it takes a little bit longer for the mouse to stop as well. This is expected and an acceptable compromise for what I was trying to achieve.

Other gestures. Mouse movement out of the way, I also played with other gestures. Click – left hand pointing down, right click – same as left click but with right hand this time, drag – moving right hand. I also implemented zoom and pan. Zooming is done by stretching both hands and moving hands closer together or far apart. Panning is done by stretching both hands and moving hands together.

Implementation notes

My code is a simple state transition from gesture to no gesture.

Note one limitation here – it is not possible to move directly from one gesture to the other. To move the mouse and make a right click, I need to move the mouse with my left hand, pull my hand away, and then make the right click gesture. Allowing gesture to gesture transitions is possible but because information from the sensor is not very accurate, deciding the gesture a user is making can be a hard choice. The fewer times this decision is done the better the user experience. For example deciding the user is making a zoom or pan gesture was tricky. When I detect two hands in the active area I know I have to do zoom or pan. Then I wait for one second and compare the position of the hands with the positions a second ago. If the distance between the hands changed considerably, a number that is found by experimentation, I decide to use zoom gesture, pan otherwise. It sounds good in theory but in practice the sensor returns inaccurate data. Any decisions relying on subtle differences may fail. In gesture to gesture transitions such tricky decisions will need to be made constantly.

My full gesture transition looks like this.

.

I have a main dispatcher that keeps an ordered list of IGestureHandler objects. The program starts in the “No Gesture” state and a dispatcher listens for updates from the sensor. When it gets an update, the dispatcher goes through a list of IGestureHandler objects calling their CanHandleEvent(). If no handler returns true, the program remains in the “No Gesture” state and the dispatcher waits for the next update. Otherwise, the first object whose CanHandleEvent() returned true will be designated as the current handler. All subsequent joint updates are forwarded to the current handler by calling its HandleMoveEvent(). This is done until the handler decides it no longer wants to handle joint updates, in which case its HandleMoveEvent() returns false and the handler is deactivated. The program then goes back to the “No Gesture” state. Some gestures are given more priority while going through handlers and deciding who should handle a gesture. For example, the two hand gesture handler is given more priority over the left hand and right hand gesture handlers or else it will never get to be active.

Here is my code in its crude, undocumented form. If it appeals I may clean it up or answer questions, and perhaps make a demo video of it. You should at least be able to run it and move the mouse with your left hand. I have made some attempt to decouple gestures from their corresponding actions. It is easy to reconfigure the left hand movement to, say, do a right click drag while pressing the shift key.

Posted in: Uncategorized

Current time and date in Ethiopia

The page time.ertale.com was a nice little project I did a few weeks ago. It shows the current Ethiopian date and time, the way Ethiopians count it.

I was looking for the current Ethiopian date. Googling returned websites with Ethiopian local time (not even in the Ethiopian way of counting time, although those websites are indeed useful for non-Ethiopians). Time aside, I couldn’t find any page that showed current month and year. Hence the motivation.

Some points,

  • After a lot of search I was almost about to dive into working out an algorithm to convert Gregorian date to Ethiopian date. But my persistent search paid off when I came across a page with everything I need at ethiopic.org. They have laid out a working algorithm there. They’ve even provided a Java program (with a unit test too!)
  • Users not having Ethiopic fonts installed is a big issue for Ethiopian websites. Everything (except the text at the footer) is shown in images, not text.
  • Using my own knowledge and after reading up some articles, I thought it’d be fair to label the 24 hour period 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

I have an idea of adding a “Holidays within the next 30 days” section at the bottom. Not sure if it’d be helpful to anyone though. If you wish the page had more features please comment.

Posted in: Uncategorized

Tags: , , , , , , , , , , , , , , , , , , , , , ,

How to hibernate windows automatically

How do you set your windows to automatically hibernate after 10 minutes? It took me about ten minutes to get this right, which I think is too long. It’ll probably take more for common users so here it is.

There seem to be many shutdown and hibernate tools out there but this is begging for trojan viruses. Besides, windows already has everything you need. Microsoft has an article on how to put your computer to hibernate. This command hibernates your computer right away,

%windir%\System32\rundll32.exe powrprof.dll,SetSuspendState

If you’d like to do execute this command after X amount of time then you can put the command in a batch file and create a scheduled task.

- Save the above command in a .bat file or use mine.
- Go to Scheduled Tasks in your control panel and add a scheduled task. Browse to the batch file when asked for the program to run.

Posted in: Uncategorized

Using curl to save to file

curl http://mirrors.enquira.co.uk/apache/xml/xalan-j/xalan-j_2_7_1-bin-2jars.zip > xalan-j_2_7_1-bin-2jars.zip

That’s right, it’s that obvious, specially for people with COIK syndrome.

Posted in: Uncategorized

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.

Posted in: Uncategorized

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 ([text isEqualToString:@"\n"]) {

 // 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>

Posted in: Objective C, iPhone

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.

Posted in: iPhone

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.

Posted in: Uncategorized

Creating Edit and Save/Done buttons in navigation bar programmatically

This took annoyingly long time to figure out. There isn’t much info on the web about it surprisingly. Table view comes with Edit/Done buttons automatically and most info out there is about table views. I had my own view with text field controls and such. I wanted to have an Edit button. When pressed, it changes to Save with blue background and displays edit controls that are normally not there. Here goes,

- (void)viewDidLoad {

  [super viewDidLoad];

  mBarButtonItemEdit = [[UIBarButtonItem alloc] initWithTitle:@&quot;Edit&quot; style:UIBarButtonItemStyleBordered target:self action:@selector(editButtonPressed)];

  mBarbuttonitemSave = [[UIBarButtonItem alloc] initWithTitle:@&quot;Save&quot; style:UIBarButtonItemStyleDone target:self action:@selector(editButtonPressed)];

  self.navigationItem.rightBarButtonItem = mBarButtonItemEdit;
}

I used a boolean inside the function editButtonPressed to know the state (Edit or Save state). Nice and easy.

Posted in: Objective C, Uncategorized, iPhone

Declare protocol before defining it?

I was looking at ways to declare a protocol without defining, if such a thing makes sense. By declare I mean somehow telling the compiler the name of a protocol only, without defining the methods that should be adopted. This is similar to @class for classes,

@class MyClass;

The reason I wanted to do so was because I had a header file that looked like this,

@interface MyClass {
 id mDelegate;
}
@property (nonatomic, retain) id mDelegate;

@end

@protocol MyDelegateProtocol
- (void) doStuff;
@end

This works fine. However, I don’t like the following because I’m not being specific on the type for mDelegate,

id mDelegate;

Being a Java programmer, I instinctively tried MyDelegateProtocol* mDelegate, which doesn’t work. The solution is to move the protocol definition before the interface declaration and use id<MyDelegateProtocol>,

@protocol MyDelegateProtocol
- (void) doStuff;
@end

@interface MyClass {
 id<MyDelegateProtocol> mDelegate;
}
@property (nonatomic, retain) id<MyDelegateProtocol> mDelegate;

@end

I then got the error

-release not found in protocol(s)

Inheriting from NSObject did the trick,

@protocol MyDelegateProtocol <NSObject>

Posted in: Objective C, Uncategorized