k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

グラフ描画ライブラリ Chart.js で凡例を表示する

JavaScriptのグラフ描画ライブラリ Chart.js

サンプルが綺麗でシンプルなためかデザイナーの方に人気のようで、いろんなブログで紹介されてます。

しかしサンプルではラベルを設定しているにも関わらず凡例が表示されておらず、「凡例を表示する機能がない」とまで書かれている記事もあって、そんなバカなと思いつつ公式ドキュメント(Chart.js | Documentation)を確認したら、ちゃんと対応されていました。

凡例にラベルを出力するには legendTemplate オプションと generateLegend() メソッドを使え

凡例にラベルを出力するには、グラフの設定オプション legendTemplate でラベルを出力するよう値を指定した上で、ChartオブジェクトのgenerateLegend()メソッドを実行し、その戻り値で得られるHTMLをどこかに差し込む必要があります。

Chart.js の設定値には全グラフ共通の Global chart configuration と 各グラフ別の設定があります。

例えば Bar Chart (Chart.Bar.js)のデフォルト設定ではこのような値が設定されています。

legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"

Chart.jsは内部で簡易のマイクロテンプレート機能を持っていて、どういう仕組みかは分かりませんが、上記のようなfor文とif文の中身を置換してくれます。

Chart.Core.js のコメントによると Javascript micro templating by John Resig が元らしい)

以下は Bar Chartで凡例を表示する例です。

<canvas id="chart_canvas" width="800" height="400"></canvas>
<ul id="chart_legend"></ul>
<script type="text/javascript" src="/static/js/Chart.js/Chart.js"></script>
<script type="text/javascript">/*<![CDATA[*/
var chart_data = {
    labels: ['9月', '10月', '11月'],
    datasets: [
        {
            label: 'りんご',
            fillColor: 'rgba(255, 0, 0, 0.5)',
            strokeColor: 'rgba(255, 0, 0, 0.75)',
            highlightFill: 'rgba(255, 0, 0, 0.75)',
            highlightStroke: 'rgba(255, 0, 0, 1)',
            data: [10, 20, 30]
        },
        {
            label: 'バナナ',
            fillColor: 'rgba(255, 255, 0, 0.5)',
            strokeColor: 'rgba(255, 255, 0, 0.75)',
            highlightFill: 'rgba(255, 255, 0, 0.75)',
            highlightStroke: 'rgba(255, 255, 0, 1)',
            data: [30, 10, 20]
        },
        {
            label: 'みかん',
            fillColor: 'rgba(255, 255, 128, 0.5)',
            strokeColor: 'rgba(255, 255, 128, 0.75)',
            highlightFill: 'rgba(255, 255, 128, 0.75)',
            highlightStroke: 'rgba(255, 255, 128, 1)',
            data: [20, 30, 10]
        }
    ]
};

var chart_context = document.getElementById('chart_canvas').getContext('2d');

var chart_option = {
    legendTemplate : "<% for (var i=0; i<datasets.length; i++){%><li><span style=\"color:<%=datasets[i].strokeColor%>\">■</span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%>"
};

var chart = new Chart(chart_context).Bar({
    labels: chart_data.labels,
    datasets: chart_data.datasets
}, chart_option);

document.getElementById('chart_legend').innerHTML = chart.generateLegend();

/*]]>*/</script>

Chart.jsでは現在のところ自動での色分け機能は持っていないので、上記コードのように個別に設定しないといけません。

また、有効な色の設定オプションはグラフによって異なりますので、サーバ側で動的に生成したデータを表示する場合、この辺も少し面倒になると思います。

(サンプルの Bar Chart の場合は、fillColorが棒グラフの塗りつぶし色、strokeColorが棒グラフの線の色、highlightFillがマウスオーバー時の塗りつぶし色、highlightStrokeがマウスオーバー時の線の色になっています。)

サーバ側でグラフの値と設定値を生成する例

自分の場合は、色の指定も含めてサーバ側で生成したJSONデータを隠しフォームに出力して、これをJavaScriptから読み込むという単純な方法にしました。

以下は簡単な例ですが、どういう構造の配列を生成すればいいかの参考にしていただければ。(Ajaxは使っていません)

<?php
$createData = function() {
    return [
        '4月' => mt_rand(0, 100),
        '5月' => mt_rand(0, 100),
        '6月' => mt_rand(0, 100),
        '7月' => mt_rand(0, 100),
        '8月' => mt_rand(0, 100),
        '9月' => mt_rand(0, 100),
        '10月' => mt_rand(0, 100),
        '11月' => mt_rand(0, 100),
        '12月' => mt_rand(0, 100),
    ];
};

$hexToRGB = function($hex) {
    return [
        hexdec(substr($hex, 0, 2)),
        hexdec(substr($hex, 2, 2)),
        hexdec(substr($hex, 4, 2)),
    ];
};

$list = [
    [
        'label' => 'りんご',
        'data' => $createData(),
        'rgb' =>  $hexToRGB('ff0000'),
    ],
    [
        'label' => 'バナナ',
        'data' => $createData(),
        'rgb' =>  $hexToRGB('ffff00'),
    ],
    [
        'label' => 'みかん',
        'data' => $createData(),
        'rgb' =>  $hexToRGB('ff8000'),
    ],
];

$chart_data = [
    'labels' => array_keys($list[0]['data']),
    'datasets' => array_reduce($list, function($dataset, $item) {
        $dataset[] = [
            'label' => $item['label'],
            'fillColor' => sprintf('rgba(%s,0.5)', implode(',', $item['rgb'])),
            'strokeColor' => sprintf('rgba(%s,0.75)', implode(',', $item['rgb'])),
            'highlightFill' => sprintf('rgba(%s,0.75)', implode(',', $item['rgb'])),
            'highlightStroke' => sprintf('rgba(%s,1)', implode(',', $item['rgb'])),
            'data' => array_values($item['data']),
        ];
        return $dataset;
    }, [])
];

$chart_json = json_encode($chart_data, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<title>Chart.js</title>
</head>
<body>
<form>
<input type="hidden" id="chart_json" value="<?=htmlspecialchars($chart_json, ENT_QUOTES, 'UTF-8')?>" />
</form>
<canvas id="chart_canvas" width="800" height="400"></canvas>
<ul id="chart_legend"></ul>
<script type="text/javascript" src="/static/js/Chart.js/Chart.js"></script>
<script type="text/javascript">/*<![CDATA[*/

var chart_json = document.getElementById('chart_json').getAttribute('value');

var chart_data = JSON.parse(chart_json);

var chart_context = document.getElementById('chart_canvas').getContext('2d');

var chart_option = {
    legendTemplate : "<% for (var i=0; i<datasets.length; i++){%><li><span style=\"color:<%=datasets[i].strokeColor%>\">■</span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%>"
};

var chart = new Chart(chart_context).Bar({
    labels: chart_data.labels,
    datasets: chart_data.datasets
}, chart_option);

document.getElementById('chart_legend').innerHTML = chart.generateLegend();

/*]]>*/</script>
</body>
</html>

出力結果はこんな感じです。

PHPのWebアプリケーションで動的にグラフ生成といえば昔は GD + JpGraph が定番で、自分もこの業界に入って最初の仕事がそれだったこともあって感慨深いのですが、HTML5やらJavaScriptの隆盛で今ではグラフはクライアント側で描画するのが当たり前になりましたね。それもこんなに簡単にかっこいいグラフが表示できるなんて。