前回の続きも記録しておく。
X軸を日付にする
X軸、Y軸ともに通常は数字が軸ラベルとして用いられる。今回私が作りたいのは時系列の情報の推移なので、X軸を数字から日付にする必要がある。
前回記載したように、CorePlotを使う場合の基本的なアプローチで下記のような設定を行った。
- ホスト設定
- グラフ設定
- プロット設定
- 軸設定
それに加えてconfigureCalendar
というカレンダー用設定処理を追加した。
NSTimeInterval const oneDay = 60.0f * 60.0f * 24.0f;
- (void)configureCalendar
{
CPTGraph *graph = self.hostview.hostedGraph;
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;
//プロットスペースのX軸表示範囲設定
CPTMutablePlotRange *xRange = [plotSpace.xRange mutableCopy];
xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(0.0f) length:CPTDecimalFromFloat(oneDay * 30)];
plotSpace.xRange = xRange;
//30日前の日付を取得する
NSDate *today = [NSDate date];
NSDate *startDate = [NSDate dateWithTimeInterval:-(oneDay * 30) sinceDate:today];
//グラフのX軸に描かれる日付を設定する
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"MM-dd"];
[formatter setLocale:[NSLocale systemLocale]];
[formatter setTimeZone:[NSTimeZone systemTimeZone]];
CPTTimeFormatter *timeFormatter = [[CPTTimeFormatter alloc] initWithDateFormatter:formatter];
timeFormatter.referenceDate = startDate;
//X軸のラベルを日付形式に設定
CPTXYAxisSet *axisSet = (CPTXYAxisSet *)graph.axisSet;
CPTXYAxis *x = axisSet.xAxis;
x.labelFormatter = timeFormatter;
x.majorIntervalLength = CPTDecimalFromFloat(7*oneDay);
x.minorTicksPerInterval = 6;
}
- プロットスペースのX軸表示範囲は、当日を最新として30日間分としたい。軸の
labelFormatter
にはCPTimeFomartterのオブジェクトを設定するため、表示の範囲としては30 × [1日の秒換算]をlengthに設定する。これは、CPTimeFomatterが秒数を使って日付を管理しているため。 - 時間的な範囲を30日前〜本日、と定義したいため、30日前の日付を取得し、CPTimeFormatterの開始日
referenceDate
プロパティに設定している。 - このグラフのX軸のラベルを日付形式に設定している。インターバルは7日間なので、この場合は
7*oneDay
秒となり、それをx.minorTicksPerInterval=6
とすることで6分割(1日ごとに目盛表示される)した。
続いて、DataSourceメソッドにてX軸の値を返却する場合も、日付フォーマットであることを考慮してindex * oneDay
としている。
-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index {
NSUInteger valueCount = [self.calendarData count];
switch (fieldEnum) {
case CPTScatterPlotFieldX:
//index * 1日分の秒数
if (index < valueCount) {
return [NSNumber numberWithUnsignedInteger:index * oneDay]; //<- here
}
break;
case CPTScatterPlotFieldY:
if ([plot.identifier isEqual:kData] == YES) {
NSNumber *numberForPlot = [[self.calendarData objectAtIndex:index] valueForKey:@"max"];
return numberForPlot;
}
default:
break;
}
return [NSDecimalNumber zero];
}
元となるデータをユーザが指定してグラフを作成する
通常、グラフはviewDidLoadまたはviewDidAppear:のタイミングで描画されることが多いように思う。
私の実現したかったことは、グラフ表示させるプロットをユーザが動的に選択(テキストフィールドで条件を指定)することだったので、UITextFieldDelegateプロトコルを実装し、textFieldDidEndEditing:
メソッド内にて、グラフ更新処理を記述した。
#pragma mark - UITextFieldDelegate
- (void)textFieldDidEndEditing:(UITextField *)textField
{
if (![self.nameTextField.text isEqualToString:@""]) {
//グラフ表示させるためのモデルデータ作成
......
//グラフ更新処理
[self reloadGraphData:self.hostview.hostedGraph];
}
}
ちなみにreloadGraphData:
は内部のメソッドで、下記のように実装。
- (void) reloadGraphData:(CPTGraph*)graph
{
// データ取得の各種delegateが実行される。
[graph reloadData];
}
連続しないデータ間を補完したプロットは作れないのか
これについては、現在調査中・・・、もとい、調査凍結中。
X軸に設定した目盛り単位にY軸の値をプロットできればその2点間は線で結ばれるが、間の値が欠落しているとそれを補完して最も近い2点間を結んでくれるわけではない。
折れ線グラフという体裁において、これはかなり違和感がある。間の値はNSNULL null
を設定していると上述のようになるのだが、@0
を設定すると一応折れ線の体は維持してくれる(本質的な解決ではない)。