ITごはん

ITを活用したい人、ITで何か作りたい人、そんなITでごはんを食べる人のためのブログ

【Objective-C】かっこいいグラフの比較

f:id:HIDEKIT:20140603174336p:plain

iPhoneアプリでかっこいいグラフを使って、数字をビジュアル化したいというニーズは沢山ありますね。 簡単に導入できるOSSグラフライブラリが沢山ありますが、その中でも割りとメジャーなものを実際に導入して、使用感・実装感を試してましょう。

今回ターゲットとしたのは、

  • PNChart
  • CorePlot
  • Highcharts

の3つです。

PNChatとCorePlotは比較的よく使用されていると思います。割合シンプルなPNChartと、かなり複雑なことも指定できるCorePlotで有名ですが、 もうひとつJavaScriptライブラリであるHighchartsもUIWebview経由で試してみます。

iPhone版+Android版両アプリを作る必要があるプロジェクトではHighchartsの利用も有力な選択肢になりますね。

なお、今回作成したソース一式は以下にあります。

導入方法

それでは早速、上記3アプリを実装してみましょう。

1. 下準備

Tabbed Applicationで新規プロジェクトを作っておきます。 今回は比較したいので、Storyboardから1つタブを追加して3タブにしておきます。(追加したタブにThirdViewControllerというViewControllerを割り当てておきます)

2. Cocoa Podで導入

Cocoa Podを使用している人は以下をPodfileに書き込んで、install一発です。

platform :ios,'6.0'
pod 'PNChart', '~> 0.5'
pod 'CorePlot', '~> 1.4'

※CocoaPodをインストールしていない人は、下記アドレスを参考にインストールするか、Git Hubからダウンロードしてプロジェクトに追加してください。

installしたら、Graph.xcworkspaceでプロジェクトを開きなおしましょう。 X-Codeからみたディレクトリは以下の様な感じになっているはずです。

f:id:HIDEKIT:20140603175700p:plain

3. PNChartの使い方

今回はFirsViewControllerに実装します。 .mファイルに、まず以下を追加です。

FirstViewController.m
#import "PNChart.h"
FirstViewController.m
- (void)viewDidLoad
{
    [super viewDidLoad];

    //For LineChart
    PNLineChart * lineChart = [[PNLineChart alloc] initWithFrame:CGRectMake(0, 135.0, SCREEN_WIDTH, 200.0)];
    [lineChart setXLabels:@[@"国語",@"数学",@"英語",@"社会",@"理科"]];
    
    // Line Chart No.1
    NSArray * data01Array = @[@80, @70.5, @100, @40, @30];
    PNLineChartData *data01 = [PNLineChartData new];
    data01.color = PNFreshGreen;
    data01.itemCount = lineChart.xLabels.count;
    data01.getData = ^(NSUInteger index) {
        CGFloat yValue = [data01Array[index] floatValue];
        return [PNLineChartDataItem dataItemWithY:yValue];
    };
    // Line Chart No.2
    NSArray * data02Array = @[@20.1, @50.1, @26.4, @30.2, @46.2];
    PNLineChartData *data02 = [PNLineChartData new];
    data02.color = PNTwitterColor;
    data02.itemCount = lineChart.xLabels.count;
    data02.getData = ^(NSUInteger index) {
        CGFloat yValue = [data02Array[index] floatValue];
        return [PNLineChartDataItem dataItemWithY:yValue];
    };
    
    lineChart.chartData = @[data01, data02];
    [lineChart strokeChart];
    
    [self.view addSubview:lineChart];
    
}

これで以下の折れ線グラフが出来ました。

f:id:HIDEKIT:20140603174601p:plain

LineChart No.1とNo.2で2つ折れ線グラフを表示させます。 chartDataにデータを入れて、strokeChartで描画です。 最後にviewに追加しましょう。 簡単です。

4. CorePlotの使い方

つぎは細かな設定が可能なCorePlotです。

ヘッダーファイルに以下にして下さい。

SecondViewController.h
#import <UIKit/UIKit.h>
#import "CorePlot-CocoaTouch.h"

@interface SecondViewController : UIViewController<CPTPlotDataSource,CPTPlotSpaceDelegate>
@property(nonatomic, strong) CPTXYGraph *barChart;
@end

つぎにメソッドファイルです。

SecondViewController.m
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //ホスティングビューの作成
    CPTGraphHostingView *hostingView = [[CPTGraphHostingView alloc] init];
    
    hostingView.collapsesLayers = NO;
    self.view = hostingView;
    
    // グラフのテーマをセット
    self.barChart = [[CPTXYGraph alloc] initWithFrame:CGRectZero];
    CPTTheme *theme = [CPTTheme themeNamed:kCPTDarkGradientTheme
];
    [self.barChart applyTheme:theme];
    hostingView.hostedGraph = self.barChart;
    
    // ボーダー設定------------------------------------------------------
    self.barChart.plotAreaFrame.borderLineStyle = nil;
    self.barChart.plotAreaFrame.cornerRadius    = 0.0f;
    // Paddings----------------------------------------------------
    self.barChart.paddingLeft   = 0.0f;
    self.barChart.paddingRight  = 0.0f;
    self.barChart.paddingTop    = 0.0f;
    self.barChart.paddingBottom = 0.0f;
    
    self.barChart.plotAreaFrame.paddingLeft   = 60.0f;
    self.barChart.plotAreaFrame.paddingTop    = 60.0f;
    self.barChart.plotAreaFrame.paddingRight  = 20.0f;
    self.barChart.plotAreaFrame.paddingBottom = 95.0f;
    
    // テキストスタイル
    CPTMutableTextStyle *textStyle = [CPTTextStyle textStyle];
    textStyle.color                = [CPTColor colorWithComponentRed:0.447f green:0.443f blue:0.443f alpha:1.0f];
    textStyle.fontSize             = 13.0f;
    textStyle.textAlignment        = CPTTextAlignmentCenter;
    
    // ラインスタイル
    CPTMutableLineStyle *lineStyle = [CPTMutableLineStyle lineStyle];
    lineStyle.lineColor            = [CPTColor colorWithComponentRed:0.788f green:0.792f blue:0.792f alpha:1.0f];
    lineStyle.lineWidth            = 2.0f;
    
    //プロット間隔の設定
    CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.barChart.defaultPlotSpace;
    plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(100)]; //0〜100点のY軸を作る
    
    plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromInt(0) length:CPTDecimalFromInt(5)];

    // X軸のメモリ・ラベルなどの設定
    CPTXYAxisSet *axisSet = (CPTXYAxisSet *)self.barChart.axisSet;
    CPTXYAxis *x          = axisSet.xAxis;
    
    x.axisLineStyle               = lineStyle;
    x.majorTickLineStyle          = lineStyle;
    x.minorTickLineStyle          = lineStyle;
    x.majorIntervalLength         = CPTDecimalFromString(@"10");
    x.orthogonalCoordinateDecimal = CPTDecimalFromString(@"0");
    x.title                       = @"科目";
    x.titleTextStyle = textStyle;
    x.titleLocation               = CPTDecimalFromFloat(5.0f);
    x.titleOffset                 = 36.0f;
    x.minorTickLength = 5.0f;
    x.majorTickLength = 9.0f;
    x.labelRotation  = M_PI / 4; // 表示角度
    x.labelTextStyle = textStyle;
    
    //X軸のラベル名(教科)
    NSMutableArray *labels = [NSMutableArray arrayWithCapacity:3];
    float idx = 0.5;
    for (NSString *year in @[@"国語",@"数学",@"英語",@"社会",@"理科"]) // ラベルの文字列
    {
        CPTAxisLabel *label = [[CPTAxisLabel alloc] initWithText:year
                                                       textStyle:axisSet.xAxis.labelTextStyle];
        label.tickLocation = CPTDecimalFromFloat(idx); // ラベルを追加するレコードの位置
        label.offset = 5.0f; // 軸からラベルまでの距離
        [labels addObject:label];
        ++idx;
    }
    // X軸に設定
    x.axisLabels = [NSSet setWithArray:labels];
    x.labelingPolicy = CPTAxisLabelingPolicyNone;

    
    
    // Y軸のメモリ・ラベルなどの設定
    CPTXYAxis *y = axisSet.yAxis;
    
    y.axisLineStyle               = lineStyle;
    y.minorTickLength = 5.0f;
    y.majorTickLength = 10.0f;
    y.majorTickLineStyle          = lineStyle;
    y.minorTickLineStyle          = lineStyle;
    y.majorIntervalLength         = CPTDecimalFromFloat(10.0f); //1メモリあたりの点数
    y.orthogonalCoordinateDecimal = CPTDecimalFromFloat(0.0f);

    y.title                       = @"点数";
    y.titleTextStyle = textStyle;
    y.titleRotation = M_PI*2;
    y.titleLocation               = CPTDecimalFromFloat(55.0f);
    y.titleOffset                 = 28.0f;
    lineStyle.lineWidth = 0.8f;
    y.majorGridLineStyle = lineStyle;
    y.labelTextStyle = textStyle;
    
    // バー表示設定
    CPTBarPlot *barPlot = [CPTBarPlot tubularBarPlotWithColor:[CPTColor colorWithComponentRed:1.0f green:1.0f blue:0.88f alpha:1.0f] horizontalBars:NO];
    
    barPlot.fill = [CPTFill fillWithColor:[CPTColor colorWithComponentRed:0.573f green:0.82f blue:0.831f alpha:0.50f]];
    
    barPlot.lineStyle = lineStyle;
    barPlot.baseValue  = CPTDecimalFromString(@"0");
    barPlot.dataSource = self;
    barPlot.barWidth = CPTDecimalFromFloat(0.5f);
    barPlot.barOffset  = CPTDecimalFromFloat(0.5f);
    [self.barChart addPlot:barPlot toPlotSpace:plotSpace];
    
}
-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
{
    return 5;
}
-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    NSDecimalNumber *num = nil;
    
    if ( [plot isKindOfClass:[CPTBarPlot class]] ) {
        
        switch ( fieldEnum ) {
                //X方向の位置を指定
            case CPTBarPlotFieldBarLocation:
                num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:index];
                break;
                
                //棒の高さを指定
            case CPTBarPlotFieldBarTip:
                if(index == 0){
                    num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:80];
                }else if(index == 1){
                    num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:30];
                }else if(index == 2){
                    num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:20];
                }else if(index == 3){
                    num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:60];
                }else if(index == 4){
                    num = (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:45];
                }
                break;
        }
    }
    return num;
}

出来ました!!

f:id:HIDEKIT:20140603174557p:plain

長いですが、その分細かく設定できますね。 以下のthemeNamedを変更すると、テーマが変わります。どんなテーマがあるかは、本家APIを参照して下さい http://core-plot.googlecode.com/hg-history/efa73ee50d8edfd0e1d4294cf4bd2c07d9f8af77/documentation/html/iOS/group__theme_names.html

    CPTTheme *theme = [CPTTheme themeNamed:kCPTDarkGradientTheme

3. Highchartsの使い方

まず、JavaScript込のHTMLファイルをプロジェクトに追加してください。 このHTMLでは、highcharts.jsとexporting.jsをcdnから読み込んでいます。 そして、グラフを描画する領域であるid="container"を用意します。

graph.html
<body>
        <script src="http://code.highcharts.com/highcharts.js"></script>
        <script src="http://code.highcharts.com/modules/exporting.js"></script>
        <div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>
</body>

Storyboad上でWebViewを配置して、ヘッダーファイルと紐付けて下さい。 ヘッダーファイルは以下の用になります。

ThirdViewController.h
#import <UIKit/UIKit.h>

@interface ThirdViewController : UIViewController<UIWebViewDelegate>

@property (weak, nonatomic) IBOutlet UIWebView *webView;

@end

続いてメソッドファイルですね。 viewDidLoadにHTMLファイルを読み込む処理を書きましょう。

ThirdViewController.m
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //忘れずデリゲート(webViewDidFinishLoadを拾うため)
    self.webView.delegate = self;
    
    //HTMLファイルの読み込み
    NSString *path = [[NSBundle mainBundle] pathForResource:@"graph" ofType:@"html"];
    NSURL *url = [NSURL fileURLWithPath:path];
    NSURLRequest *req = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:req];
    
}

今回は、あくまでObjective-Cから渡した値をもとに、 グラフを作成して欲しいので、stringByEvaluatingJavaScriptFromStringで、 HTML側のJavaScriptファンクションを呼び出します。 この時にグラフで使用するデータを渡しています。

ThirdViewController.m
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    
    //実行するjsのファンクションと、dataを配列で渡す
    NSString *js = @"draw([10,20,30,40,50]);";

    [self.webView stringByEvaluatingJavaScriptFromString:js];
    
}

HTML側のdrawメソッドが呼び出されてます。

graph.html
//サンプルの即時実行から変更
//Obj-C側でdrawを呼び出す
function draw(item) {
            //alert(item);
            $('#container').highcharts({
                                       chart: {
                                       type: 'column'
                                       },
                                       title: {
                                       text: '期末テストの結果'
                                       },
                                       subtitle: {
                                       text: '中学2年生 二学期'
                                       },
                                       xAxis: {
                                       categories: [
                                                    '国語',
                                                    '数学',
                                                    '英語',
                                                    '社会',
                                                    '理科'
                                                    ]
                                       },
                                       yAxis: {
                                       min: 0,
                                       title: {
                                       text: '点数'
                                       }
                                       },
                                       tooltip: {
                                       headerFormat: '<span style="font-size:10px">{point.key}</span><table>',
                                       pointFormat: '<tr><td style="color:{series.color};padding:0">{series.name}: </td>' +
                                       '<td style="padding:0"><b>{point.y:.1f} mm</b></td></tr>',
                                       footerFormat: '</table>',
                                       shared: true,
                                       useHTML: true
                                       },
                                       plotOptions: {
                                       column: {
                                       pointPadding: 0.2,
                                       borderWidth: 0
                                       }
                                       },
                                       series: [{
                                                name: '山田太郎',
                        //ここでObj-C側から取得したデータをセットします。
                                                data: [item[0], item[1], item[2], item[3], item[4]]
                                                
                                                }]
                                       });
        };

こんな感じです。 実際は棒グラフが下からニョキニョキ伸びてきます。色使いも綺麗です。

f:id:HIDEKIT:20140603174552p:plain

おわりに

今回は成績を折れ線グラフや棒グラフで表示するだけの簡単なものでしたが、実際は各ライブラリともかなり綺麗で、かっこいいグラフのサンプルが沢山あるので、そちらのサンプルを見ながらどれを使うか決めたらいいと思います。 ただ、Android対応を考えるとHighchartsはお勧めです。(ほかにもjsのグラフライブラリ沢山ありますけどね)

リンク

Highcharts http://www.highcharts.com/