iOS(とかOSX)でグラフを書けるCorePlotいいよー。オプションとかいろいろあって煩雑だけどそこはさておいて簡潔にまとめてみようと思います。
まずは導入編
CocoaPods使いましょう。
pod 'CorePlot'
からの$ pod install
でいけます。
散布図、折れ線グラフ
サンプルとしてグラフは2つ書きました。Aがオプションをできるだけ省略したほう、Bがオプションをいくつか付けてみたほうです。配列に20個のランダムな整数値を入れ、配列のインデックスを横軸、その値を縦軸にとった折れ線のグラフを描画しました。
ちゃんと使うにはグラフ用のUIViewクラスを作ってラップしたほうがいいと思いますが、今回はそれも面倒なのでViewControllerに直接書きます!
散布図を載せたいViewControllerを作ってヘッダーにはプロトコルの指定とプロット用データ置き場を書き加えました。Objective-CのArrayはちょっと面倒なので、、、なお、C++のコードなのでViewControllerの拡張子は.mm
にしておく必要があります。
#import <UIKit/UIKit.h>
#import <CorePlot/CorePlot-CocoaTouch.h>
#import <vector>
@interface ScatterViewController : UIViewController<CPTPlotDataSource>
{
std::vector<int> _dataA;
std::vector<int> _dataB;
}
@end
次に実装ファイルのほう。
グラフ領域やオプションなどの初期設定はviewDidLoad
で行いました。
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
// CorePlotのセットアップ
[self setupPlotA];
[self setupPlotB];
}
グラフA
次にグラフA用のセットアップです。
- (void)setupPlotA
{
CGRect rect = CGRectMake(0, 100, 320, 100);
CPTGraphHostingView* hostingView = [[CPTGraphHostingView alloc] initWithFrame:rect];
CPTGraph* graph = [[CPTXYGraph alloc] initWithFrame:rect];
hostingView.hostedGraph = graph;
CPTScatterPlot* plot = [[CPTScatterPlot alloc] init];
plot.identifier = @"A";
plot.dataSource = self;
[graph addPlot:plot];
CPTXYPlotSpace* space = (CPTXYPlotSpace*)graph.defaultPlotSpace;
space.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(100)];
space.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(19)];
[self.view addSubview:hostingView];
// プロット用データのセットアップ
_dataA.clear();
for(int i=0; i<20; ++i){
_dataA.push_back(rand()%100);
}
}
いくつかCorePlotのクラスが登場しました。
- CPTGraphHostingView
グラフが描画されるUIViewで親のViewに
addSubview
されます - CPTGraph グラフのメタデータ?的なものです。グラフ全体を管理しているイメージ
- CPTScatterPlot データをどのように処理するか(描画するか)ということを扱っているクラス(というイメージ)
大雑把に言うと、CPTScatterPlot
をCPTGraph
にセットして、CPTGraph
をCPTGraphHostingView
にセット、そのCPTGraphHostingVIew
をaddSubview
するという流れです。
グラフB
グラフBではオプションをいくつか設定しました。本当はもっとたくさんのオプションがあるのですが、把握しきれないため適当にいくつか選んでいじっています。
- (void)setupPlotB
{
CGRect rect = CGRectMake(0, 210, 320, 200);
CPTGraphHostingView* hostingView = [[CPTGraphHostingView alloc] initWithFrame:rect];
CPTGraph* graph = [[CPTXYGraph alloc] initWithFrame:rect];
hostingView.hostedGraph = graph;
CPTScatterPlot* plot = [[CPTScatterPlot alloc] init];
plot.identifier = @"B";
plot.dataSource = self;
[graph addPlot:plot];
CPTXYPlotSpace* space = (CPTXYPlotSpace*)graph.defaultPlotSpace;
space.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(100)];
space.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(19)];
[self.view addSubview:hostingView];
// プロット用データのセットアップ
_dataB.clear();
for(int i=0; i<20; ++i){
_dataB.push_back(rand()%50);
}
// いろいろ設定
graph.plotAreaFrame.paddingBottom = 50;
graph.plotAreaFrame.paddingLeft = 50;
graph.plotAreaFrame.paddingTop = 10;
graph.plotAreaFrame.paddingRight = 10;
// 軸の設定
CPTXYAxisSet* axisSet = (CPTXYAxisSet*)graph.axisSet;
CPTMutableLineStyle* style = [CPTMutableLineStyle lineStyle];
style.lineColor = [CPTColor orangeColor];
style.lineWidth = 2;
axisSet.xAxis.axisLineStyle = style;
axisSet.yAxis.axisLineStyle = style;
CPTXYAxis* xAxis = axisSet.xAxis;
xAxis.majorIntervalLength = CPTDecimalFromInt(5);
NSNumberFormatter* tickFormatter = [[NSNumberFormatter alloc] init];
[tickFormatter setGeneratesDecimalNumbers:NO];
[tickFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
xAxis.labelFormatter = tickFormatter;
xAxis.title = @"X軸";
CPTXYAxis* yAxis = axisSet.yAxis;
yAxis.majorIntervalLength = CPTDecimalFromInt(20);
yAxis.title = @"Y軸";
// プロットの設定
plot.dataLineStyle = nil;
plot.plotSymbol = [CPTPlotSymbol diamondPlotSymbol];
}
すごく長くなってしまうのがわかりますね。正直、ここは結構面倒です。
DataSourceのメソッド
さきほどまでのコードでは、肝心のグラフに載せるためのデータはほとんど登場しません。せいぜいランダムな値をvectorに追加したぐらい。CorePlotではデータを返すメソッドを使います。TableViewみたいな感じで。
numberForPlot
プロット用の値で、ScatterPlotの場合はidxに対応したXの値またはYの値を返しますnumberOfRecordsForPlat
プロットの項目数を返してあげます
- (NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx
{
NSNumber* num;
switch (fieldEnum) {
case CPTScatterPlotFieldX:
num = [NSNumber numberWithInt:idx];
break;
case CPTScatterPlotFieldY:
// plot.identifierで分岐することで、2種類以上のグラフを同じDataSourceクラスで書くことができる
if([(NSString*)plot.identifier isEqualToString:@"A"]){
num = [NSNumber numberWithInt:_dataA[idx]];
}else{
num = [NSNumber numberWithInt:_dataB[idx]];
}
break;
}
return num;
}
- (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
{
if([(NSString*)plot.identifier isEqualToString:@"A"]){
return (int)_dataA.size();
}
return (int)_dataB.size();
}
今回の例ではグラフA、Bという2つのグラフが登場します。これらはどちらも同じDataSourceメソッドを使うことになるので、plot.identifier
を使ってどちらのグラフのための情報を返すときなのかを判別して値を返しています。
円グラフ
こんな感じのグラフを書けます。
ヘッダーファイル
ヘッダーはこんな感じ。基本は似通っています。
#import <UIKit/UIKit.h>
#import <CorePlot/CorePlot-CocoaTouch.h>
#import <vector>
@interface PieViewController : UIViewController<CPTPieChartDataSource,CPTPieChartDelegate>
{
std::vector<float> _data;
}
@end
実装ファイル
実装はこんな感じです。CPTScatterPlot
の変わりにCPTPieChart
が登場し(どちらも同じ親クラスを持ちます)、それに関連した設定が必要となっています。
また、CPTTheme
という概念があって、グラフのビジュアルをいくつかのテーマに基づいて切り替えることができます。
- (void)setupChartA
{
CGRect rect = CGRectMake(10, 40, 300, 300);
CPTGraphHostingView* hostingView = [[CPTGraphHostingView alloc] initWithFrame:rect];
CPTGraph* graph = [[CPTXYGraph alloc] initWithFrame:rect];
graph.axisSet = nil;
hostingView.hostedGraph = graph;
[self.view addSubview:hostingView];
CPTTheme* theme = [CPTTheme themeNamed:kCPTDarkGradientTheme];
[graph applyTheme:theme];
CPTPieChart* plot = [[CPTPieChart alloc] init];
plot.identifier = @"PIE";
plot.dataSource = self;
plot.delegate = self;
[graph addPlot:plot];
plot.pieRadius = 100;
_data.clear();
_data.push_back(50);
_data.push_back(20);
_data.push_back(15);
_data.push_back(3);
_data.push_back(12);
}
データソース
円グラフの場合は2次元ではないので返す値は場合分けの必要はありません。
- (NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx
{
return [NSNumber numberWithFloat:_data[idx]];
}
棒グラフ
こんなグラフを書けます。使ったテーマが悪いのか、めちゃくちゃシンプルだな、、
長くなったのでソースは省略します。以下にサンプルアプリ一式があるのでどぞー。 pi-chan/CorePlotExample