ガントチャートの作成 ： Yellowfin と FusionCharts の活用でプロジェクト管理

以前、[Google チャートと連携してガントチャートを作成する手順]( https://qiita.com/ExtremeCarvFJ/items/df29cc5870e02d4ef733)を紹介しました。
今回は、FusionChart との連携でガントチャートを作成してみようと思います。

## 1. 事前準備

#### 1-1. ビューの作成まで

[前回記事](https://qiita.com/ExtremeCarvFJ/items/ad453a6110cec093bdb9)  を参考に、『1-2. ビューの作成』 までの作業を実施します。

以下の手順で取り扱うデータを[こちら]( https://github.com/ChasingHH/ShareFiles/blob/Share/projectProgress.csv)に共有しています。ダウンロードしてご利用ください。

上記から取得したデータを全てビューで参照できるようにします。
1 点、日付データに関しては、FusionCharts 側で設定する書式に合わせてください。今回の場合は yyyy/MM/dd の書式で取り扱うため、サンプルデータの [start] [end] は上記書式に設定する必要があります。ビューで書式を修正する手順に関しては[こちら]( https://wiki.yellowfin.co.jp/pages/viewpage.action?pageId=2293966)をご確認ください。

## 2. グラフの作成
準備が整ったら、グラフの作成に手順を進めます。新規でレポートを作成し、以下の手順に従ってグラフを作成します。

#### 2-1. [データ] ステップ
ビューに設定した軸と集計値を全てテーブルに配置します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/c8481494-bd3e-a867-275c-b64d45f31841.png)

#### 2-2. [グラフ] ステップ
[グラフ] ステップに進み、画面右側 [グラフの選択] から [JavaScriptグラフ] を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/c0b406ee-3419-61f7-7f82-846ed39cc2c5.png)

##### JavaScriptタブ
雛形を全て削除し、以下のコードに置き換えます。コードの処理の内容は後ほど説明します。
```javascript:JS
generateChart = function(options) {
    var $chartDrawDiv = $(options.divSelector);
    var processedTask = processTask(options.dataset.data);
    var processedConnector = processConnector(options.dataset.data);
    var processedMilestone = processMilestone(options.dataset.data);
    var processedProcess = processProcess(options.dataset.data);
    var processedCategory = processCategory(options.dataset.data);
    doDrawing(processedTask, processedConnector, processedMilestone, processedProcess, processedCategory, $chartDrawDiv);
},

processTask = function(dataset) {
    var ds_task = [];
    for (j=0;j<dataset.type.length;j++){
        if(dataset.type[j].formatted_data === "task"){
            ds_task.push({
                label: dataset.label[j].formatted_data,
                processid: dataset.processid[j].formatted_data,
                start: dataset.start[j].formatted_data,
                end: dataset.end[j].formatted_data,
                id: dataset.id[j].formatted_data,
                color: dataset.color[j].formatted_data,
                height: dataset.height[j].formatted_data,
                toppadding: dataset.toppadding[j].formatted_data,
            });
        }
    }
    return ds_task;
},

processConnector = function(dataset) {
    var ds_connector = [];
    for (k=0;k<dataset.type.length;k++){
        if(dataset.type[k].formatted_data === "connector"){
            ds_connector.push({
                fromtaskid: dataset.fromtaskid[k].formatted_data,
                totaskid: dataset.totaskid[k].formatted_data,
                color: dataset.color[k].formatted_data,
                thickness: dataset.thickness[k].formatted_data,
            });
        }
    }
    return ds_connector;
},

processMilestone = function(dataset) {
    var ds_milestone = [];
    for (n=0;n<dataset.type.length;n++){
        if(dataset.type[n].formatted_data === "milestone"){
            ds_milestone.push({
                date: dataset.start[n].formatted_data,
                taskid: dataset.id[n].formatted_data,
                tooltext: dataset.label[n].formatted_data,
                color: dataset.color[n].formatted_data,
                shape: dataset.shape[n].formatted_data,
            });
        }
    }
    return ds_milestone;
},

processProcess = function(dataset) {
    var ds_process = [];
    for (i=0;i<dataset.type.length;i++){
        if(dataset.type[i].formatted_data === "process"){
            ds_process.push({
                label: dataset.label[i].formatted_data,
                id: dataset.id[i].formatted_data,
            });
        }
    }
    return ds_process;
},

processCategory = function(dataset) {
    var ds_category = [];
    for (l=0;l<dataset.type.length;l++){
        if(dataset.type[l].formatted_data === "category"){
            ds_category.push({
                label: dataset.label[l].formatted_data,
                start: dataset.start[l].formatted_data,
                end: dataset.end[l].formatted_data,
            });
        }
    }
    return ds_category;
},

doDrawing = function(ds_task, ds_connector, ds_milestone, ds_process, ds_category,  $chartDrawDiv) {
    console.log(JSON.stringify(ds_task));
    console.log(JSON.stringify(ds_connector));
    console.log(JSON.stringify(ds_milestone));
    console.log(JSON.stringify(ds_process));
    console.log(JSON.stringify(ds_category));
    require(['js/chartingLibraries/fusioncharts-suite-xt/js/fusioncharts.js'], function(){
        const dataSource = {
            chart: {
                caption: "プロジェクト計画",
                dateformat: "yyyy/mm/dd",
                ganttwidthpercent: "60",
                ganttpaneduration: "40",
                ganttpanedurationunit: "w",
                useverticalscrolling: "0",
                plottooltext: "<b>$label</b><br>開始: <b>$start</b><br>終了: <b>$end</b>",
                theme: "fusion"
            },
            tasks: [{task: ds_task}],
            connectors: [{connector: ds_connector}],
            milestones: [{milestone: ds_milestone}],
            processes: {
                headertext: "タスク",
                align: "center",
                process: ds_process
            },
            categories: [{category: ds_category}]
        };
        FusionCharts.ready(function() {
            var myChart = new FusionCharts({
                type: "gantt",
                renderAt: $chartDrawDiv[0],
                width: 800,
                height: 400,
                dataFormat: "json",
                dataSource
            }).render();
        });
    });
};
```
##### プレビュー
[プレビュー] に移り、どのようなチャートが作成されるか確認します。マウスをホバーすると、ツールチップが表示されます。ところでこのプロジェクト、開発実装フェーズの遅延が原因で、もう少しで検収が来期に先送りされるところでしたね。やばいやばい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/bc47def5-f6ac-24e6-541c-d83406ca3e73.png)

## 3. コードの内容
動作概要が分かったところで、コードの中身を見てみましょう。
##### generateChart
```JavaScript:generateChart
generateChart = function(options) {
    var $chartDrawDiv = $(options.divSelector);
    var processedTask = processTask(options.dataset.data);
    var processedConnector = processConnector(options.dataset.data);
    var processedMilestone = processMilestone(options.dataset.data);
    var processedProcess = processProcess(options.dataset.data);
    var processedCategory = processCategory(options.dataset.data);
    doDrawing(processedTask, processedConnector, processedMilestone, processedProcess, processedCategory, $chartDrawDiv);
},
```

JavaScript グラフの処理において、最初に呼び出されるのが generateChart = function(options) です。
[データ] ステップでテーブルに追加した情報が、options に格納されて、メソッドに受け渡されます。
`var $chartDrawDiv = $(options.divSelector)` でグラフを出力する div を選択します。
`var processedTask = processTask(options.dataset.data); `
`var processedProcess = processProcess(options.dataset.data); `
`var processedCategory = processCategory(options.dataset.data); `
`var processedConnector = processConnector(options.dataset.data); `
`var processedMilestone = processMilestone(options.dataset.data); `
`doDrawing(processedTask, processedProcess, processedCategory, processedConnector, processedMilestone, $chartDrawDiv);`
上記 6 行で、別の関数を呼び出して、データ処理とグラフ描画処理を行っています。呼び出し先の関数での処理内容は後続の個所で説明をします。

##### processTask
```JavaScript:processTask
processTask = function(dataset) {
    var ds_task = [];
    for (j=0;j<dataset.type.length;j++){
        if(dataset.type[j].formatted_data === "task"){
            ds_task.push({
                label: dataset.label[j].formatted_data,
                processid: dataset.processid[j].formatted_data,
                start: dataset.start[j].formatted_data,
                end: dataset.end[j].formatted_data,
                id: dataset.id[j].formatted_data,
                color: dataset.color[j].formatted_data,
                height: dataset.height[j].formatted_data,
                toppadding: dataset.toppadding[j].formatted_data,
            });
        }
    }
    return ds_task;
},
```

Yellowfin のデータセットを JSON の配列に変換して ds_task にオブジェクトとして格納しています。
今回のサンプルデータであれば、以下のような JSON の書式になります。各タスクの予定、実績、遅延の状況を水平バーで示すために必要な情報です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/9fd23f73-ad56-a086-79f6-2e169b4172f4.png)

画面は [グラフ] ステップでディベロッパーツールのコンソールから確認したものです。コードの中に `console.log(JSON.stringify(ds_task))` を記述しておくと、データオブジェクトの中身をコンソールで確認することができます。今回の場合は、doDrawing の先頭に記述しています。デバッグに便利なので、活用してみてください。

以下の手順では、上記と同じ要領で、ガントチャートを描くために必要なデータを各関数の中データセットで取り込んでいく流れになります。機能毎にどのようなデータが必要となるかは、[こちら]( https://www.fusioncharts.com/dev/chart-guide/standard-charts/gantt-chart)で詳しく紹介しています。これ以降の説明と合わせてご確認ください。

##### processConnector
```JavaScript:processConnector
processConnector = function(dataset) {
    var ds_connector = [];
    for (k=0;k<dataset.type.length;k++){
        if(dataset.type[k].formatted_data === "connector"){
            ds_connector.push({
                fromtaskid: dataset.fromtaskid[k].formatted_data,
                totaskid: dataset.totaskid[k].formatted_data,
                color: dataset.color[k].formatted_data,
                thickness: dataset.thickness[k].formatted_data,
            });
        }
    }
    return ds_connector;
},
```
各タスクの依存関係を示すためのデータを、JSON の配列に変換して ds_connector にオブジェクトとして格納しています。

##### processMilestone
```JavaScript:processMilestone
processMilestone = function(dataset) {
    var ds_milestone = [];
    for (n=0;n<dataset.type.length;n++){
        if(dataset.type[n].formatted_data === "milestone"){
            ds_milestone.push({
                date: dataset.start[n].formatted_data,
                taskid: dataset.id[n].formatted_data,
                tooltext: dataset.label[n].formatted_data,
                color: dataset.color[n].formatted_data,
                shape: dataset.shape[n].formatted_data,
            });
        }
    }
    return ds_milestone;
},
```
マイルストーン（キックオフと検収）を表示するためのデータを、JSON の配列に変換して ds_milestone にオブジェクトとして格納しています。

##### processProcess
```JavaScript:processProcess
processProcess = function(dataset) {
    var ds_process = [];
    for (i=0;i<dataset.type.length;i++){
        if(dataset.type[i].formatted_data === "process"){
            ds_process.push({
                label: dataset.label[i].formatted_data,
                id: dataset.id[i].formatted_data,
            });
        }
    }
    return ds_process;
},
```
チャート左の「タスク」（キックオフから検収まで）欄の情報を描画するためのデータを、JSON の配列に変換して ds_process にオブジェクトとして格納しています。

##### processCategory

```JavaScript:processCategory
processCategory = function(dataset) {
    var ds_category = [];
    for (l=0;l<dataset.type.length;l++){
        if(dataset.type[l].formatted_data === "category"){
            ds_category.push({
                label: dataset.label[l].formatted_data,
                start: dataset.start[l].formatted_data,
                end: dataset.end[l].formatted_data,
            });
        }
    }
    return ds_category;
},
```
時系列 (1月～3月) の情報を描画するためのデータを、JSON の配列に変換して ds_category にオブジェクトとして格納しています。

##### doDrawing
```JavaScript:doDrawing
doDrawing = function(ds_task, ds_connector, ds_milestone, ds_process, ds_category,  $chartDrawDiv) {
    console.log(JSON.stringify(ds_task));
    console.log(JSON.stringify(ds_connector));
    console.log(JSON.stringify(ds_milestone));
    console.log(JSON.stringify(ds_process));
    console.log(JSON.stringify(ds_category));
    require(['js/chartingLibraries/fusioncharts-suite-xt/js/fusioncharts.js'], function(){
        const dataSource = {
            chart: {
                caption: "プロジェクト計画",
                dateformat: "yyyy/mm/dd",
                ganttwidthpercent: "60",
                ganttpaneduration: "40",
                ganttpanedurationunit: "w",
                useverticalscrolling: "0",
                plottooltext: "<b>$label</b><br>開始: <b>$start</b><br>終了: <b>$end</b>",
                theme: "fusion"
            },
            tasks: [{task: ds_task}],
            connectors: [{connector: ds_connector}],
            milestones: [{milestone: ds_milestone}],
            processes: {
                headertext: "タスク",
                align: "center",
                process: ds_process
            },
            categories: [{category: ds_category}]
        };
        FusionCharts.ready(function() {
            var myChart = new FusionCharts({
                type: "gantt",
                renderAt: $chartDrawDiv[0],
                width: 800,
                height: 400,
                dataFormat: "json",
                dataSource
            }).render();
        });
    });
};
```

`require[]` の中で、JavaScriptライブラリーを格納したディレクトリーを相対パスで記述します。
`dataSource` `chart` の中で、タイトルやフォントなどのチャートの見た目を定義しています。
`dateformat: "yyyy/mm/dd"` で、日付の書式を定義しています。ビューで日付を定義する場合は、この書式と合わせる必要があります。
`tasks: [{task: ds_task}],`
`connectors: [{connector: ds_connector}],`
`milestones: [{milestone: ds_milestone}],`
`processes: {headertext: "タスク", align: "center", process: ds_process},`
`categories: [{category: ds_category}]`
上記 5 行で、先の手順でデータセットに取り込んだオブジェクトの中身を FushionCharts に受け渡しています。

`FusionCharts.ready` でチャートを描画します。
`type: "gantt”,` でチャートの種類を指定します。

`renderAt: $chartDrawDiv[0]` でチャートを描画する div を指定します。`$chartDrawDiv` はコードの先頭で宣言した変数です。
`width` と `height` はダッシュボードの大きさなどに合わせてピクセル単位で指定します。FusionCharts のサンプルを見ると、%で指定していますが、Yellowfin で扱う場合は、ピクセルで指定した方がきれいに表示されます。

## 4. 最後に
今回は、極めてシンプルなガントチャートの作成手順を紹介いたしました。ガントチャートの作成に当たっては、プロジェクトごとにタスク種類や期間など、条件に応じて描画する内容を柔軟に変更する必要があります。Yellowfin が参照するデータの設計が重要になってくると考えます。

Anyway, ライブラリーとの連携で、Yellowfin の用途も広がりますね。
では皆様、良いデータ分析とプロジェクト管理を! Cheers!!