LoginSignup
33
36

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-05-15

概要

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

機能概要

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

動作イメージ

フィルタ条件に対応したJSONをAjaxで出力し、d3.jsによりSVG領域を再描画します。d3.jsの処理が高速なので、ストレス無く快適に操作できます。
バーンダウンチャート.gif

JSONデータの例

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

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メソッド以下は折れ線グラフの描画、データのポイントにサークルを描画しマウスオーバー時に表示する数値の格納を行います。

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

33
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
36