Displaying a Date Axis in an iOS Chart

Max Rivlin [Infragistics] / Tuesday, March 25, 2014

A date axis is essential when it comes to displaying date-based data in a chart. Often times you don't really need an actual date axis to show dates; you can simply display date strings as labels. This is a good approach when you simply want to provide a description for your data points and don't care that the distance between all the points is identical, even though the time span between those points isn't. However, that's not a true date scale.

When using a date scale, the distance between the points matters. If you plotted the first day of every month in a year on a date scale, you would expect the time span for January to be a bit larger than for February. 

In this post I will talk about creating a date axis using IGChartView and NucliOS. IGCategoryDateTimeXAxis is a special kind of a category axis that acts more like a numeric axis. It's typically used with series that show continuous data, such as line/spline/area type series.

As a side note, a column series doesn't usually make a good fit for a time axis. Columns normally have a fixed width. On a date scale that width suggests that each column has a start and end date, even though we probably meant to have a column represent one particular point in time. Besides, seeing random clusters of columns overlapping each other may not be the prettiest sight.

So, let's create some data and throw it into the chart. First, let's define a simple object model for our data.

@interface DataModel : NSObject
@property (nonatomic, copy) NSString *label;
@property (nonatomic, retain) NSDate *date;
@property (nonatomic) CGFloat value;

-(id)initWithValue:(CGFloat)value date:(NSDate*)date label:(NSString*)label;

@end

And now for the actual data:

-(void)prepareData:(int)numberOfPoints
{
    NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
    [formatter setDateFormat:@"MMM dd"];    

    for (int i=0; i<numberofpoints; i="" br="">    {
        CGFloat value = arc4random()%10;
        NSTimeInterval month = 3600 * 24 * 30;
        NSDate *date = [NSDate dateWithTimeIntervalSinceNow:month * sqrt(i)];
        NSString *label = [formatter stringFromDate:date];
        DataModel *item = [[DataModel alloc]initWithValue:value date:date label:label];
        [_data addObject:item];
    }
}

The data is random, with each consecutive point happening after a shorter time interval.

Let's also define a couple of items in our view controller, such as the chart, the data source helper and an array to hold our data.

@interface ViewController : UIViewController<IGChartViewDelegate>
{
    IGChartView *_chart;
    IGCategoryDateSeriesDataSourceHelper *_dsh;
    NSMutableArray *_data;
}
@end

Now, let's set up our chart with a line series and a pair of axes.

- (void)viewDidLoad
{
    [super viewDidLoad];    

    _data = [[NSMutableArray alloc]init];

    [self prepareData:10];

    _dsh = [[IGCategoryDateSeriesDataSourceHelper alloc]initWithData:_data valuePath:@"value" andDatePath:@"date"];
    _dsh.labelPath = @"label";

    _chart = [[IGChartView alloc]initWithFrame:CGRectInset(self.view.frame, 20, 20)];
    _chart.delegate = self;    

    IGCategoryDateTimeXAxis *xAxis = [[IGCategoryDateTimeXAxis alloc]initWithKey:@"xAxis"];
    xAxis.extent = 80;
    xAxis.labelOrientationAngle = 90;
    xAxis.displayType = IGTimeAxisDisplayTypeContinuous;    

    IGNumericYAxis *yAxis = [[IGNumericYAxis alloc]initWithKey:@"yAxis"];
    yAxis.minimum = 0;
    yAxis.maximum = 10;    

    IGLineSeries *series = [[IGLineSeries alloc]initWithKey:@"series"];
    series.xAxis = xAxis;
    series.yAxis = yAxis;
    series.dataSource = _dsh;
    series.thickness = 5;    

    [_chart addAxis:xAxis];
    [_chart addAxis:yAxis];
    [_chart addSeries:series];    

    [self.view addSubview:_chart];
}

You can see that our view controller conforms to IGChartViewDelegate protocol. This gives us access for a bunch of handy methods and I'm going to use one of them to give the date labels a nice format. Also, note that the data source helper primarily uses the value and date properties of the data model, yet we're also setting a labelPath property. Doing so is necessary in one special case. By default the time axis will be broken down into constant time intervals, but what if you wanted to show a label under each of the data points? You can change the axis display type to Discrete and the chart will pull the labels from labelPath, while using the dates to create the scale.

Here's the final piece that formats the axis labels.

-(NSString *)chartView:(IGChartView *)chartView labelForAxis:(IGAxis *)axis withItem:(NSObject *)item
{
    if ([item isKindOfClass:[NSNumber class]])
    {
        NSNumber *yAxisLabel = (NSNumber*)item;
        return [yAxisLabel stringValue];
    }    

    if ([item isKindOfClass:[NSDate class]])
    {
        NSDate *xAxisLabel = (NSDate*)item;
        NSDateFormatter *formatter = [[NSDateFormatter alloc]init];
        [formatter setDateFormat:@"MMM dd"];
        return [formatter stringFromDate:xAxisLabel];
    }    

    if ([item isKindOfClass:[IGCategoryDatePoint class]])
    {
        IGCategoryDatePoint *point = (IGCategoryDatePoint*)item;
        return point.label;
    }    

    return nil;
}

The result can be seen in this screenshot:

DateTimeAxis

And here's what the axis would look like with a discrete display type:
DateTimeAxisDiscrete

You can also download the project file to see the complete code here: UsingDateTimeAxis.zip

You will need NucliOS to be installed on your machine, which you can get here

By Max Rivlin