HighChartsを使って時系列のグラフを描く

状況説明

グラフと言っても CPU 使用率とか web サイトの生死確認とかばかりなので、ずっと rrdtool_ でやってきた。 もうちょっと凝ったものは python /numpy /scipy /pandas /matplotlib で作ってた。

けれども、JavaScript で描いたインタラクティブなのが欲しいと言われてしまったので 、事実上の標準と言われる HighCharts でやってみた。

HighCharts は商用利用の場合は有償なのでご注意願いたい。 非営利の場合は 販売サイト の Non-commercial に該当するので無料で使えるということだと思われるが、その辺は自分で確かめてください。

まあ、ちょっと思うのは、修正 BSD ライセンスで使える(と思うがご自分でご確認願います) D3 のほうがいろいろできて良かったかも。 もちろん HighCharts も必要十分というか、使い切れないくらいの機能があるわけだけれども。

最小限のグラフ

なにはともあれ、最小限のグラフが これ。独立のページ にも出しておく。

highchart1
highchart1.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<html>
<head>
    <title>highchart1</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script src="//code.highcharts.com/highcharts.js"></script>
</head>
<body>
    <div id="chart1"></div>
    <script type="text/javascript">
    function draw1() {
        options = {
            chart: {renderTo: 'chart1'},
            series: [{data: [1,1,2,3,5,8,13,21,34]}]
        };
        chart = new Highcharts.Chart(options);
    };
    document.body.onload = draw1();
    </script>
</body>
</html>

5,6 行目で jquery.min.js と highcharts.js を取り込んでいる。

  • highcharts.js が依存するので jQuery も必要。
  • scheme (http: とか https: とか) を書くと https scheme から http な .js を読み込むことになってうまく動かない場合があるので、こんなふうに書く(というか書かない)のが正解らしい。

9行目の div はグラフを描画する場所の指定。id の chart1 に後述の renderTo: をあわせること。

11-18行目が HighCharts 的ソースコードで、まずは関数 draw1() の定義。

  • options の中にグラフの属性をいろいろ書く。
  • 13 行目で chart の描画先 (renderTo:) を chart1 と指定
  • 14 行目の series はデータ系列を置く。
  • 16 行目では、この options の設定を使って HighCharts.Chart なオブジェクトを生成。

そして、18 行目でボディのロード時に draw() を呼び出すことで、グラフを描画させる。

少し属性を追加

タイトルとか軸の名称なんかを入れてみる。 ついでに2系列のデータも入れてみる。 見難くなったので、グラフの一部を矩形に選択すればズームするようにした。 さらに、グラフを画像として保存できるようにした。 (グラフ右上隅のメニュー「三」を見よ)

highchart2.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<script src="//code.highcharts.com/modules/exporting.js"></script>
<script src="//code.highcharts.com/modules/offline-exporting.js"></script>

<div id="chart2" style="width:400px; height:300px"></div>
 
<script type="text/javascript">
    function draw2() {
        options = {
            chart: {renderTo: 'chart2',
                    zoomType:'xy'},
            title: {text: 'Fibonacci vs. Tribonacci'},
            xAxis: {title: {text: 'step'}},
            yAxis: {title: {text: 'value'}},
            series: [{name: 'Fibonacci',  data: [1,1,2,3,5,8,13,21,34,55,89,144,233,377]},
                     {name: 'Tribonacci', data: [0,0,1,1,2,4,7,13,24,44,81,149,274,504]}]
        };
        chart = new Highcharts.Chart(options);
    };
    document.body.onload = draw2();
</script>
  • 1,2 行目で exporting.js と offline-exporting.js を読み込んでいる。これは通常はChart1 のソースの <head></head> 内、jQuery と highcharts の読み込みの直後に置く。
  • 前者があると一応グラフの画像保存が可能だが、HighCharts 側のサーバを使う関係で http/https 境界跨ぎ問題が出る。
  • 後者を含めて2行書くとブラウザ側で処理するので境界跨ぎ問題が出ない。
  • 4 行目の id が chart2 になっていて、さっきの chart1 と衝突しない点に注意。
  • 10 行目、ズームができるように zoomType を追加。”x” にすると水平方向には narrowing ができるが垂直方向にはできない。
  • 11-13 行目は、それぞれタイトルを追加している。
  • 15 行目、ふたつめのデータ系列は series 配列の中に列挙すれば追加できる。

時系列データを CSV から

まずは時系列データにする。

highchart3.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div id="chart3" style="width:400px; height:300px"></div>
 
<script type="text/javascript">
    function draw3() {
        options = {
            chart: {renderTo: 'chart3',
                    zoomType:'xy'},
            title: {text: '我が体重'},
            xAxis: {title: null,
                    type: 'datetime'},
            yAxis: {title: {text: 'weight (kg)'}},
            series: [{name: 'weight',
                      data: [[Date.parse('2016-07-29'), 59.8],
                             [Date.parse('2016-07-30'), 60.2],
                             [Date.parse('2016-07-31'), 59.9],
                             [Date.parse('2016-08-02'), 60.8],
                             [Date.parse('2016-08-05'), 61.7],]}]
        };
        chart = new Highcharts.Chart(options);
    };
    document.body.onload = draw3();
</script>
  • 9,10行目のxAxisに type: ‘datetime’ を書くことで時系列の X 軸になる。
  • 13行目から。この時データは [日時データ, 数値] の配列を配列に並べることになる。

さて、このデータを外部の CSV ファイルから読み込む。

highchart4.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div id="chart4" style="width:400px; height:300px"></div>
 
<script type="text/javascript">
    function draw4() {
        options = {
            chart: {renderTo: 'chart4',
                    zoomType:'xy'},
            title: {text: '我が体重'},
            xAxis: {title: null,
                    type: 'datetime'},
            yAxis: {title: {text: 'weight (kg)'}},
        };
        var content = [];
        var result = [];
        var count1 = [];
        var count2 = [];
        jQuery.get('/_static/highcharts/highchart4.csv', function(content) {
            content = content.split('\n');
            for(i=0; i<content.length-1; i++) {
                count1[i] = [];
                count2[i] = [];
                result[i] = content[i].split(',');
                result[i] = [Date.parse(result[i][0]), Number(result[i][1]), Number(result[i][2])];
                count1[i][0] = result[i][0];
                count1[i][1] = result[i][1];
                count2[i][0] = result[i][0];
                count2[i][1] = result[i][2];
            }
            options['series'] = [{name: '俺', data: count1}, {name: '目標', data: count2}];
            chart = new Highcharts.Chart(options);
        });
    };
    document.body.onload = draw4();
</script>
highchart4.csv
1
2
3
4
5
2016-07-29,59.8,70.0
2016-07-30,60.2,70.0
2016-07-31,59.9,70.0
2016-08-02,60.8,70.0
2016-08-05,61.7,70.0
  • options に静的に data を与えるのをやめて、17行目の jQuery.get で取ってくる。
  • 取ってくる元はここでは /_static/highcharts/highchart4.csv になっているが、要するにブラウザ側から見てこの位置 (URL) に当該 CSV ファイルが有る(読める)ことが必要。Sphinx の関係で妙なpathになっているが普通は ‘./highchart4.csv’ とかかな。
  • 無名関数内で苦闘しているのは Javascript の良い書き方がわからないため。もっと美しい書き方があるはず。
  • 29行目、opetions[‘series’] に CSV から得たデータを書き込む。
  • 29,30 行目の2行は jQuery.get() の外側 (31行目の後、32行目の前) でも良いはずだと思うのだけれど、そっちに移すと動作しない。なんでや。

備考

  • 2016/Jul/29-31 ごろ書いた。