d3.jsで作ったバーンダウンチャート

## 概要
d3.jsを使ってバーンダウンチャートを作ってみました。短いコードでデータをビジュアル化できるd3.jsは素晴らしいですね!!　いろいろ試した結果、出来上がったJavaScript部分のコードをこちらに掲載します。

## 機能概要
サーバサイドからJSONデータを受け取りd3.jsを使ってSVG領域にバーンダウンチャートを描画します。予定線をグレー、実績線をオレンジ、工数合計線をグリーンとして折れ線グラフを作ります。各データのポイント部はマウスオーバー時に数値を表示します。

## 動作イメージ
フィルタ条件に対応したJSONをAjaxで出力し、d3.jsによりSVG領域を再描画します。d3.jsの処理が高速なので、ストレス無く快適に操作できます。
![バーンダウンチャート.gif](https://qiita-image-store.s3.amazonaws.com/0/78230/e66f227e-fc3b-a40c-5101-1a29f584c1f7.gif)

## JSONデータの例
データベースにはチケット毎に以下の項目を時系列に格納しています。各項目を日付毎にサマライズしたJSONを生成しd3.jsに渡します。
* Day: 日付
* Total: 工数合計
* Planned: 予定
* Earned: 実績

```json:example.json
[
    {"Day":"2016/03/05","Total":30.0,"Planned":30.0,"Earned":28.0},
    {"Day":"2016/03/06","Planned":20.0},
    {"Day":"2016/03/07","Planned":10.0}
]
```

## JavaScriptコードの例
コードの前半はsvg領域の初期化、jsonのパース、スケールの設定、軸の描画、当日線の描画を行います。drawメソッド以下は折れ線グラフの描画、データのポイントにサークルを描画しマウスオーバー時に表示する数値の格納を行います。

```js:BurnDowns.js
func.drawBurnDown = function () {
    var $svg = $('#BurnDown');
    if ($svg.length !== 1) {
        return;
    }
    $svg.empty();
    var dataSet = JSON.parse($('#BurnDownJson').val());
    if (dataSet.length === 0) {
        $svg.hide();
        return;
    }
    $svg.show();
    var svg = d3.select('#BurnDown');
    var padding = 40;
    var axisPadding = 70;
    var width = parseInt(svg.style('width'));
    var height = parseInt(svg.style('height'));
    var bodyWidth = width - axisPadding - (padding);
    var bodyHeight = height - axisPadding - (padding);
    var minDate = new Date(d3.min(dataSet, function (d) { return d.Day; }));
    var maxDate = new Date(d3.max(dataSet, function (d) { return d.Day; }));
    var dayWidth = (bodyWidth - padding) / dateDiff('d', maxDate, minDate);
    var xScale = d3.time.scale()
        .domain([minDate, maxDate])
        .range([padding, bodyWidth]);
    var yScale = d3.scale.linear()
        .domain([d3.max(dataSet, function (d) {
            return d.Total !== undefined || d.Earned !== undefined
                ? Math.max.apply(null, [d.Total, d.Planned, d.Earned])
                : d.Planned;
        }), 0])
        .range([padding, bodyHeight])
        .nice();
    var xAxis = d3.svg.axis()
        .scale(xScale)
        .tickFormat(d3.time.format('%m/%d'))
        .ticks(10);
    var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');
    svg.append('g')
        .attr('class', 'axis')
        .attr('transform', 'translate(' + axisPadding + ', ' + (height - axisPadding) + ')')
        .call(xAxis)
        .selectAll('text')
        .attr('x', -20)
        .attr('y', 20)
        .style('text-anchor', 'start');
    svg.append('g')
        .attr('class', 'axis')
        .attr('transform', 'translate(' + axisPadding + ', 0)')
        .call(yAxis)
        .selectAll('text')
        .attr('x', -20);
    var now = axisPadding + xScale(new shortDate());
    var nowLineData = [
        [now, axisPadding - 40],
        [now, yScale(0) + 20]];
    var nowLine = d3.svg.line()
        .x(function (d) { return d[0]; })
        .y(function (d) { return d[1]; });
    svg.append('g').attr('class', 'now').append('path').attr('d', nowLine(nowLineData));
    draw('total', 0, dataSet.filter(function (d) { return d.Total !== undefined; }));
    draw('planned', 1, dataSet.filter(function (d) { return d.Planned !== undefined; }));
    draw('earned', 2, dataSet.filter(function (d) { return d.Earned !== undefined; }));

    function draw(css, n, ds) {
        var line = d3.svg.line()
            .x(function (d) {
                return (dateDiff('d', new Date(d.Day), minDate) * dayWidth)
                    + axisPadding + padding;
            })
            .y(function (d) {
                return yScale(prop(d));
            });
        var g = svg.append('g').attr('class', css);
        g.append('path').attr('d', line(ds));
        g.selectAll('circle')
            .data(ds)
            .enter()
            .append('circle')
            .attr('cx', function (d, i) { return i * dayWidth + axisPadding + padding })
            .attr('cy', function (d) { return yScale(prop(d)); })
            .attr('r', 4)
            .append('title')
            .text(function (d) { return prop(d); });

        function prop(d) {
            switch (n) {
                case 0: return d.Total;
                case 1: return d.Planned;
                case 2: return d.Earned;
            }
        }
    }
}
```

## ご参考
このコード例は「.NETで動くチケット管理ツール : プリザンター」で使用しています。サーバサイドのコード(ASP.NET C#)やCSSは、こちらをご参照ください。
https://github.com/Implem/Implem.Pleasanter

## チャートの具体的な使い方
プロジェクトマネジメントにおける実践的なデータ活用例
http://qiita.com/Implem/items/112bfd38433f7f5d7bb8