PIYO - Tech & Life -

iOS用グラフ描画ライブラリCorePlot超入門

グラフ iOS

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 データをどのように処理するか(描画するか)ということを扱っているクラス(というイメージ)

大雑把に言うと、CPTScatterPlotCPTGraphにセットして、CPTGraphCPTGraphHostingViewにセット、そのCPTGraphHostingVIewaddSubviewするという流れです。

グラフ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