React Nativeでチャート(グラフ)を描画するためのライブラリがいくつかあります。例えばこのへんとか↓

JesperLekland/react-native-svg-charts: 📈 One library to rule all charts for React Native 📊

とはいうものの、できることが少なかったりなかなか思うようにいかないことがあったりします。

WebにもJSのチャート系ライブラリはいくつかあります。例えば有償のHighchartsなんかはかなり協力です。これから登場するChart.jsのようにMITライセンスのものもあります。

そういったWebのライブラリを使ったほうが案外良い結果になることもあるかも、という提言として今回の例を紹介してみます。

React NativeにはWebViewがあるため、Javascriptを有効にした状態で通常のWebと同じHTMLをレンダリングすることが可能です。そのHTML内でChart.jsをロードし、Webでのようにデータを登録することでグラフを描画できてしまいます。

ちょっと長いですが、まずコードから見てみます。

 1import React, { Component, PropTypes } from 'react'
 2import { View, WebView, Dimensions } from 'react-native'
 3
 4const { width } = Dimensions.get('window');
 5
 6export default class ChartView extends Component {
 7  chartHTML() {
 8    return `<html>
 9        <head>
10          <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
11          <style media="screen" type="text/css">#container{width:100%;height:100%;top:0;left:0;right:0;bottom:0;position:absolute;}</style>
12            <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.min.js"></script>
13            <script>
14              document.addEventListener("DOMContentLoaded", function() {
15                var ctx = document.getElementById("container").getContext('2d');
16                var myChart = new Chart(ctx, {
17                    type: 'bar',
18                    data: {
19                        labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
20                        datasets: [{
21                            label: '# of Votes',
22                            data: [12, 19, 3, 5, 2, 3],
23                            backgroundColor: [
24                                'rgba(255, 99, 132, 0.2)',
25                                'rgba(54, 162, 235, 0.2)',
26                                'rgba(255, 206, 86, 0.2)',
27                                'rgba(75, 192, 192, 0.2)',
28                                'rgba(153, 102, 255, 0.2)',
29                                'rgba(255, 159, 64, 0.2)'
30                            ],
31                            borderColor: [
32                                'rgba(255,99,132,1)',
33                                'rgba(54, 162, 235, 1)',
34                                'rgba(255, 206, 86, 1)',
35                                'rgba(75, 192, 192, 1)',
36                                'rgba(153, 102, 255, 1)',
37                                'rgba(255, 159, 64, 1)'
38                            ],
39                            borderWidth: 1
40                        }]
41                    },
42                    options: {
43                        scales: {
44                            yAxes: [{
45                                ticks: {
46                                    beginAtZero:true
47                                }
48                            }]
49                        }
50                    }
51                });
52            });
53            </script>
54        </head>
55        <body>
56            <canvas id="container" width="${width}" height="${width}" />
57        </body>
58    </html>`
59  }
60
61  render() {
62    const html = this.chartHTML();
63    return (
64      <View style={this.props.style}>
65        <WebView
66          style={{flex: 1, backgroundColor: 'transparent'}}
67          source={{ html: html, baseUrl: 'web/' }}
68          originWhitelist={['*']}
69          javaScriptEnabled
70          domStorageEnabled
71          scalesPageToFit
72          scrollEnabled={false}
73          automaticallyAdjustContentInsets
74        />
75      </View>
76    )
77  }
78}

render()では実質的にWebViewしかありません。そこでsourceプロパティにhtml文字列を渡します。中身はchartHTML()に書いてあります。

<body>の中に<canvas>が1個だけあり、<script>に描画内容が書いてあるという感じです。割と力技です。

中身はこちらのドキュメントのサンプルをそのまま転載しています。

で、画面はというと次のようになります。

タップ操作で自然に反応してくれるため思ったより使い勝手が良いです。

ただし、今のコードはCDNからライブラリを読むためネットワークが必要です。オフラインでも使えるようなアプリにする場合はライブラリのコードをなんとかしてアプリに入れておく必要があるので工夫が必要のはず。うまくいかない可能性もありそう。未検証です。

canvasに描画するようなタイプのライブラリを使いたいような場合には案外現実的な選択肢にしてもいいのではないかと個人的には思います。