ガントチャート（JSライブラリ： jquery.ganttView）をカスタマイズしてみた（いろんな形状（星・矢印・丸）でスケジュール管理）

# はじめに

今となっては古のJSライブラリ

https://github.com/thegrubbsian/jquery.ganttView

上記ライブラリのスケジュール管理オブジェクトを、いろんな形状で置いてみたので、勘所をメモ書きしておきます。

ニッチな内容だけど、誰かの参考になれば幸いです。

全然、こんなニッチな物、誰も読まないだろうと思ってメモ書きしたら、意外に閲覧数があったので、第２弾です。（根強い人気があるんですかね？）


https://qiita.com/Syoji_Yonemoto/items/f3dc7587aa50b3c523d7

ソースコードは、前回のカスタマイズ（上記）の反映した上での改修となっています。

# 色んな形状って？

![ganttChart.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/281768/bf80489d-9113-d7e4-f69a-0b24ea4dc116.jpeg)

上記のような、丸、星、シャフトのついた矢印など、様々な図形でスケジュールを引けるように改造しました。

# 入力データの変更

各データに、type属性を追加して、図形の形を入力データで指示できるように変更します。

```json
	{
		id: 2, name: "Feature 2", series: [
			{ name: "Planned", start: new Date(2010,00,05), end: new Date(2010,00,20), type: "box" },
			{ name: "Actual", start: new Date(2010,00,06), end: new Date(2010,00,17), type: "arrow", color: "#F15B55" },
			{ name: "Projected", start: new Date(2010,00,06), end: new Date(2010,00,17), type: "box", color: "#e0e0e0" },
			{ name: "Projected", start: new Date(2010,00,19), end: new Date(2010,00,19), type: "star", color: "#FFC20E" },
			{ name: "Projected", start: new Date(2010,00,21), end: new Date(2010,00,21), type: "circle", color: "#007394" }
		]
	}
```
サンプルデータの一部を抜粋してくると、上記のようなイメージです。

# 追加のスタイルシート

```css
div.ganttview-arrow-cells {
	position: relative;
	height: 25px;
}

div.ganttview-star-clippath {
    position: relative;
    width: 25px;
    height: 25px;
    clip-path: polygon(50% 5%, 61% 40%, 98% 40%, 68% 62%, 79% 96%, 50% 75%, 21% 96%, 32% 62%, 2% 40%, 39% 40%);
}

div.ganttview-circle-clippath {
    position: relative;
    width: 25px;
    height: 25px;
    clip-path: circle(41% at 50% 50%);
}

div.ganttview-arrow-cell-clippath {
    position: absolute;
    height: 25px;
    clip-path: polygon(0 33%, 100% 33%, 100% 67%, 0 67%);
}

div.ganttview-arrow-triangle-cell-clippath {
    position: absolute;
    width: 25px;
    height: 25px;
    clip-path: polygon(0 0, 100% 50%, 0 100%, 30% 50%);
}

div.ganttview-arrow-circle-cell-clippath {
    position: absolute;
    width: 25px;
    height: 25px;
    clip-path: circle(41% at 50% 50%);
}

div.ganttview-star-cell-clippath {
    position: absolute;
    width: 25px;
    height: 25px;
    clip-path: polygon(50% 5%, 61% 40%, 98% 40%, 68% 62%, 79% 96%, 50% 75%, 21% 96%, 32% 62%, 2% 40%, 39% 40%);
}

div.ganttview-circle-cell-clippath {
    position: absolute;
    width: 25px;
    height: 25px;
    clip-path: circle(41% at 50% 50%);
}

```

clip-path を使って、いろんな図形を作るというのは、他にも記事をアップしてる方がいますので、jquery.ganttViewのカスタムの方に焦点を当てて、clip-pathに深くは触れません。

# 改修ポイント編（javascript)

前回、手をいれた「addBlocks」を、更に改造していきます。

# addBlocks ：管理オブジェクト描画の全体制御

```javascript
function addBlocks(div, data, cellWidth, start) {
    //  ～省略
    // 入力データのループ
    for (var i = 0; i < data.length; i++) {
        for (var j = 0; j < data[i].series.length; j++) {
            var series = data[i].series[j];

            if("複数オブジェクトのキー" in series && series."複数オブジェクトのキー".length > 0){
                var tasks;
                tasks = jQuery("<div>", {
                            "class": "ganttview-block-cells"
                });
                rowIdx = addRowDataForTasks(div, tasks, data[i], series."複数オブジェクトのキー", cellWidth, start, rows, rowIdx);        
            } else {
                rowIdx = addRowData(div, data[i], series, cellWidth, start, rows, rowIdx);
            }
        }
    }
}
```

スケジュール管理オブジェクトを置くところは、全部、関数に切り出してしまいます。

1行に１管理オブジェクトを設置するようの関数（元々）

ですが、、、矢印型は、1行複数オブジェクト（丸型と箱型とシャフト型の組み合わせ）なので専用関数を呼び出します。

# addRowData ：　1行データの描画

```javascript

        function addRowData(div, series, data, cellWidth, start, rows, rowIdx) {

            var styleName = judgeDisplayStyleType(data, false);

            var obj;
            if(CHART_TYPE.ARROW == data.type){
                var arrowObjs = jQuery("<div>", {
                    "class": "ganttview-arrow-cells"
                });
                obj = getArrowBlock(div, series, data, cellWidth, start, rows, styleName, arrowObjs);
            } else {
                obj = getEachBlock(div, series, data, cellWidth, start, rows, styleName, false);
            }
            
            jQuery(rows[rowIdx]).append(obj);

            rowIdx = rowIdx + 1;

            return rowIdx;
        }


```

こっちは、第１段で改造した、１行に複数の管理オブジェクトを設置する関数です。

# addRowDataForTasks  １行に複数タスクを描画する用の関数

```javascript
        function addRowDataForTasks(div, tasks, series, taskList, cellWidth, start, rows, rowIdx) {

            for(var i = 0; i < taskList.length; i++){
                var data = taskList[i];
                var styleName = judgeDisplayStyleType(data, true);
                if(CHART_TYPE.ARROW == data.type){
                    tasks = getArrowBlock(div, series, data, cellWidth, start, rows, styleName, tasks);
                }else{
                    var blockObj = getEachBlock(div, series, data, cellWidth, start, rows, styleName, false);
                    tasks.append(blockObj);    
                }
            }
            
            jQuery(rows[rowIdx]).append(tasks);
            rowIdx = rowIdx + 1;

            return rowIdx;
        }
```

こちらでも矢印型は、（丸型と箱型とシャフト型の組み合わせ）なので専用関数を呼び出します。

# judgeDisplayStyleType 図形毎の専用スタイルシート呼び分け

```javascript
        function judgeDisplayStyleType(data, multiObjectsFlag) {
            // デフォルト宣言
            var styleName = "ganttview-block";
            if(multiObjectsFlag){
                styleName = "ganttview-block-cell";
            }
            
            // 引き渡された指定値で表示する形状を変更
            if("type" in data && data.type){
                if(multiObjectsFlag){
                    // 1行内に複数の管理オブジェクトを配置
                    if(CHART_TYPE.BOX == data.type){
                        styleName = "ganttview-block-cell";
                    } else if(CHART_TYPE.ARROW == data.type){
                        styleName = "ganttview-arrow-cell-clippath";
                    } else if(CHART_TYPE.STAR == data.type){
                        styleName = "ganttview-star-cell-clippath";
                    } else if(CHART_TYPE.CIRCLE == data.type){
                        styleName = "ganttview-circle-cell-clippath";
                    }
                }else{
                    // 1行内に１行の管理オブジェクトを配置
                    if(CHART_TYPE.BOX == data.type){
                        styleName = "ganttview-block";
                    } else if(CHART_TYPE.ARROW == data.type){
                        // 矢印型は複数オブジェクト前提のため cell付きのスタイルを利用する
                        styleName = "ganttview-arrow-cell-clippath";
                    } else if(CHART_TYPE.STAR == data.type){
                        styleName = "ganttview-star-clippath";
                    } else if(CHART_TYPE.CIRCLE == data.type){
                        styleName = "ganttview-circle-clippath";
                    }
                }
            }
            return styleName;
        }
```
addRowDataとaddRowDataForTasksの関数で、それぞれの図形に合わせたスタイルシートを取得している為、追加したスタイルシートの、どれを利用するかサンプルデータの中の図形の指定でスタイルシートを切り替えます。

# getArrowBlock  矢印型を描画するには、３つのオブジェクト（丸型・箱型・シャフト型）がいるので特殊処理を実装

```javascript
        function getArrowBlock(div, series, data, cellWidth, startPlanDate, rows, styleName, objs) {
            
            var startPointObj = Object.create(data);
            var endPointObj = Object.create(data);

            // 補正用に開始日・終了日を退避する
            startPointObj['arrow_start'] = data.start;
            startPointObj['arrow_end'] = data.end;
            // 開始日基準で終了日を上書きする
            startPointObj.end = data.start;

            // 補正用に開始日・終了日を退避する
            startPointObj['arrow_start'] = data.start;
            startPointObj['arrow_end'] = data.end;
            // 終了日基準で開始日を上書きする
            endPointObj.start = data.end;

            // 強制的に丸型の箱をオブジェクトを用意する
            var startPontBlock = getEachBlock(div, series, startPointObj, cellWidth, startPlanDate, rows, "ganttview-arrow-circle-cell-clippath", true);
            // 矢印型を用意する
            var arrowObjBlock = getEachBlock(div, series, data, cellWidth, startPlanDate, rows, styleName, false);
            // 強制的に終端の矢印箱オブジェクトを用意する
            var endPointBlock = getEachBlock(div, series, endPointObj, cellWidth, startPlanDate, rows, "ganttview-arrow-triangle-cell-clippath", true);

            objs.append(startPontBlock);
            objs.append(arrowObjBlock);
            objs.append(endPointBlock);

            return objs;
        }

```

描画しようとしてるデータのディープコピーを作って、開始日・終了日を騙して、始点（丸型）と終点（シャフト型）を描画するようにして、組み合わせて矢印に見えるようにしています。

# getEachBlock  各種図形の位置決め、微調整、描画

```javascript
        
        function getEachBlock(div, series, data, cellWidth, start, rows, styleName,  camouflageDateFlag) {
            
            var size = DateUtils.daysBetween(data.start, data.end ) + 1;
            var offset = DateUtils.daysBetween(start, data.start);
            
            // 初期化
            var widthVal = 0;
            var marginLeftVal = 0;

            widthVal = ((size * cellWidth) - 5);
            marginLeftVal = (offset * cellWidth) + 1;
            
            // 丸型と星型の時は、正方形になるように幅を調整する
            if(CHART_TYPE.STAR == data.type || CHART_TYPE.CIRCLE == data.type){
                widthVal = 25;
            }

            // 矢印型で始点・終点用の箱を用意する時は微調整する
            if(CHART_TYPE.ARROW == data.type &&  camouflageDateFlag){
                widthVal = 25;
                var originArrowSize = DateUtils.daysBetween(data.arrow_start, data.arrow_end) + 1;
                size = originArrowSize;
            }

            // 矢印型の時で真ん中の箱は左横のマージンと横幅を微調整
            if(CHART_TYPE.ARROW == data.type && ! camouflageDateFlag){
                widthVal = widthVal - 5; 
                marginLeftVal = marginLeftVal + 7;
            }

            let title = data.name ;
            
            var block = jQuery("<div>", {
                "title": title + ", " + size + " 日間",
                "class": styleName,
                "css": {
                    "width": widthVal + "px",
                    "margin-left": marginLeftVal + "px",
                    "left" : "0px",
                    "right" : "0px",
                }
            });
            
            // 背景色指定をデータで受け取る
            if("color" in data && data.color){
                block.css("background-color", data.color);
            }

            if(CHART_TYPE.BOX == data.box){
                block.append(jQuery("<div>", { "class": "ganttview-block-text" }).text(size));
            }

            addBlockData(block, data, series);
            
            return block;
        }
```

既存の jquery.gannttViewの箱型を描画する為の処理が色濃く残ってるのが、この関数ですね。
図形によって、微調整（矢印型の重ね合わせ位置の調整、星型・丸型の縦幅・横幅の均一化）をしています。