おじさんにモテるファンネルチャートのつくりかた

こんにちは。d3.jsアドベントカレンダーの１８日目です。

突然ですが、おじさんに絶大な人気を誇るチャートをご存知でしょうか。
それは**「ファンネルチャート」**です。

【管理職すきなチャートアンケート（当社調べ）】

![ブック1.png](https://qiita-image-store.s3.amazonaws.com/0/72884/8434c76c-f252-a78e-097a-6b78aff2c099.png)

２位のパイプラインチャートとはつまりファンネルチャートのことなので、合わせると半数以上のおじさんが好きなチャートはファンネルで決まり！　なのです。

なんかはやりのキュレーションメディアっぽい書き出しになりましたがこの記事はそういうのではありません。

# ファンネルチャートとは？

ファンネルチャートとは、その名のとおりファネル（じょうご）のかたちをしたチャートです。データの表し方は積み上げ棒チャートに似ていて、全体の総和に対して各ステップの比率をそれぞれの高さで表します。というかまんま積み上げ棒と同じです。違うところは、棒が一本しかないところと、「先細り感」が表現できるところです。

この「先細り感」が非常に重要です。例えば営業の案件データがあるとします。１０００件の案件のうち、受注できたものは2件だったとします。全体の案件のうち、テレアポをしたもの、顧客から反応があったもの、実際に営業が赴いたもの、見積もりがきたもの、というように絞りこんでいくと、当たり前ですがどんどん数が減っていきます。ファンネルチャートを使えば、案件が受注に至るまでのゴールへの道のりがどんなふうに険しいものか先細りの様子を見て取ることができます。そこには毎日テレアポをした新入社員や客先で邪魔だから帰れといわれても無言で首を横に降ったベテラン営業マンのドラマが垣間見れます。チャートを見て「先細ってるな！」と思ったら、それがファンネルチャートです。

# なぜおじさんはファンネルチャートが好きなのか

おじさんはわかりやすいものが好きです。そしてわかったふうになることが大好きなのです。ファンネルチャートは棒が一本しかないので非常にわかりやすいです。そして棒が先細っている様子を見ると、なんかプロセスが進行している感じがあります。表現しているものが積み上げ棒と同じだとしてもそんなことは関係ありません。社会はプロセスでなりたっています。

あなたが人事部門で採用を担当しているとします。採用には書類審査、一次面接、二次面接というようにわかりやすいプロセスがあります。上司はあなたに、最近採用はどうなっているかと尋ねます。あなたは得意げに、今のところ応募は116件、内定は16人ですねと答えます。その言葉をきいて上司は失望するでしょう。上司が知りたいのはそれがどう先細っているか、つまりファンネルチャートが見たいのです。採用面接に関する情報はサイボウズ社のkintoneで管理しているという設定にします。なぜそうしたかというとkintoneでファンネルチャートを出すのがこの記事の目的だからで完全に自己都合です。ちなみにkintoneにはファンネルチャートを表示する機能がありません。ごめんなさいここからがようやく本題です。

# kintoneとD3.jsでファンネルチャートをつくってみる

kintoneのアプリマーケットには採用面接管理アプリがあるのでこれをそのまま使います。

![アプリの設定.png](https://qiita-image-store.s3.amazonaws.com/0/72884/80e3f35b-046e-fdb0-02a4-c7db04f5ff84.png)

一覧にカスタムビューを追加します。このへんはD3.jsと関係ないのではしょります。

「JavaScript / CSSでカスタマイズ」から、以下のふたつのJSを読み込みます。あと適当にjQueryも読み込みます。

D3.js
https://d3js.org/d3.v4.js
D3Funnel
https://cdn.rawgit.com/jakezatecky/d3-funnel/v1.0.0/dist/d3-funnel.js

D3.jsには標準でファンネルチャートの機能がないのですが、D3Funnelを使うことでファンネルチャートを使うことができます。

https://github.com/jakezatecky/d3-funnel

カスタムビューでファンネルチャートを表示するカスタムJSを書きます。よく考えたらkintoneには集計APIがありませんでした。がんばって各ステータスごとの件数を持ってくることにします。

```js:funnel.js
jQuery.noConflict();

var VIEW_ID = 5309320;
(function($) {
    "use strict";
    kintone.events.on("app.record.index.show", function(e) {
       if (e.viewId != VIEW_ID) {
            return;
        }
        $.when(
            getCount(''),
            getCount('合否＿1次 in ("合格")'),
            getCount('合否＿2次 in ("合格")'),
            getCount('合否＿最終 in ("合格")')
        ).done(function(cnt1, cnt2, cnt3, cnt4) {
            var data = [
				['応募', cnt1],
				['一次合格', cnt2],
				['二次合格', cnt3],
				['採用', cnt4]
			];
            drawFunnel(data);
        });
        
    });
    
    var getCount = function(query) {
        var d = new $.Deferred();
        
        query += " limit 1";
        kintone.api('/k/v1/records', 'GET', {app: kintone.app.getId(), query: query, totalCount: true}, function(resp) {
            d.resolve(resp.totalCount);
        });
        
        return d.promise();
    };
    
    var drawFunnel = function(data) {
		
		var options = {
			chart: {
				bottomWidth: 3 / 8,
				curve: { enabled: true },
				bottomPinch: 0,
			},
			block: {
				minHeight: 50,
				dynamicHeight: true,
				dynamicSlope: true,
				barOverlay: false,
				highlight: true,
			},
			label: {
				format: '{l}\n{f}',
			},
		};


		(new D3Funnel('#funnel')).draw(data, options);
    };
})(jQuery);

```

![funnel1.png](https://qiita-image-store.s3.amazonaws.com/0/72884/e008951c-7a1a-8fcd-ab36-1a6d30bc8b71.png)

おお、チャートが表示できました。見事に先細り感が表現できています。

# バブルチャートもついでに出してみる

これだけだと寂しいじゃないかと上司に言われるかもしれません。
せっかくなので、内定者の情報もいっしょに出してみることにします。来年からいっしょに働くわけですから、上司もあらかじめ顔と名前くらいは知っておく必要があります。

ただ出しても面白くないので、いいねの数が多い内定者が目立つようにバブルチャートで表示してみます。

いいね！　プラグイン
https://cybozudev.zendesk.com/hc/ja/articles/203711920

```js:bubble.js
jQuery.noConflict();

var VIEW_ID = 5309320;
(function($) {
    "use strict";
    kintone.events.on("app.record.index.show", function(e) {
       ...
        
        getRecords('合否＿最終 in ("合格")').done(function(records) {
            drawBubble(records);
        });
        
    });
    
    var getRecords = function(query) {
        var d = new $.Deferred();
        
        kintone.api('/k/v1/records', 'GET', {app: kintone.app.getId(), query: query, totalCount: true}, function(resp) {
            d.resolve(resp.records);
        });
        
        return d.promise();
    };
    
    var drawBubble = function(data) {
        var diameter = 460;
        
        var svg = d3.select("#bubble").append("svg")
            .attr("width", diameter)
            .attr("height", diameter)
            .attr("class", "bubble");
        
        var format = d3.format(",d");

        var pack = d3.pack()
            .size([diameter, diameter])
            .padding(1.2);

        var root = d3.hierarchy(classes({children: data}))
            .sum(function(d) { return d.value; });

        var node = svg.selectAll(".node")
            .data(pack(root).leaves())
            .enter().append("g")
            .attr("class", "node")
            .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

        node.append("circle")
            .attr("id", function(d) { return d.id; })
            .attr("r", function(d) { return d.r; })
            .style("fill", function(d) { return "#ffcccc"; });
            
        node.append("text")
            .attr("dy", ".3em")
            .style("text-anchor", "middle")
            .text(function(d) { return d.data.name; });
    };
    
    function classes(root) {
        var classes = [];

        function recurse(name, node) {
            if (node.children) node.children.forEach(function(child) { recurse(node.name, child); });
            else classes.push({name: node["氏名"].value, id: node["$id"].value, url: node.url.value, value: node.like.value});
        }

        recurse(null, root);
        return {children: classes};
    };
})(jQuery);

```
![funnel3.png](https://qiita-image-store.s3.amazonaws.com/0/72884/609ca0ec-08b4-c6e4-51ec-6e104f6ace61.png)

# バブルチャートに画像を表示する

明らかに、いいねが多いレコードが偏っているため、上司もおいこの内定者はどんな子だと聞いてきます。こうなればもうしめたものです。バブルチャートの中身をプロフィール画像に変えてみましょう。

```js:bubble_img.js    
    var drawBubble = function(data) {
        ...
        var node = svg.selectAll(".node")
            .data(pack(root).leaves())
            .enter().append("g")
            .attr("class", "node")
            .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

        node.append("image")
            .attr("xlink:href", function(d){
                return  d.url;
            })
            .attr("width", function(d) { 
                return d.r * 2;
            })
            .attr("height", function(d) { 
                return d.r * 2;
            })
            .attr('transform', function(d) { 
                return 'translate(-' + d.r + ',-' + d.r + ')';
                
            })
            .attr('class', function(d) { 
                return "image";
            });
    };
```

![funnel2.png](https://qiita-image-store.s3.amazonaws.com/0/72884/f9f0cab1-d675-72d2-8df4-24b7645ae8c2.png)

# おしまい

kintoneとD3.jsを組み合わせて、かんたんにファンネルチャートが表示できることがわかりました。
これで人事部長からもこいつはわかってるやつと見られること間違いなしです。
では良いお年をお迎えください。