Laravelにガントチャートを実装する方法

Laravelでガントチャートを実装するにあたり丸１日かかったので、次回から困らないように備忘録として残します。

#目次
[環境](#環境)
[ライブラリ](#ライブラリ)
[手順](#手順)
[1.ガントチャートで使うコントローラーなどを作成](###1.ガントチャートで使うコントローラーなどを作成)
[2.ガントチャートを表示するページのルーティング](###2.ガントチャートを表示するページのルーティング)
[3.ガントチャートを表示するためのコントローラー](###3.ガントチャートを表示するためのコントローラー)
[4.ビューを作成](###4.ビューを作成)
[5.ガワが表示されるか確認](###5.ガワが表示されるか確認)
[6.テーブルを作成](###6.テーブルを作成)
[7.データベースにテストデータを追加](###7.データベースにテストデータを追加)
[8.データベースから値を取得してJSONで返す処理](###8.データベースから値を取得してJSONで返す処理)
[9.api.phpにもAPI用のルーティング](###9.api.phpにもAPI用のルーティング)
[10.ガントチャートが表示されるか確認](###10.ガントチャートが表示されるか確認)
[11.ガントチャートの保存・変更・削除](###11.ガントチャートの保存・変更・削除)
[12.保存・変更・削除ができるか確認](###12.保存・変更・削除ができるか確認)
[おまけ](#おまけ)

#環境
・iMac Big Sur
・Laravel8
・MAMP

#ライブラリ
[DHTMLX Gantt](https://dhtmlx.com/?_ga=2.45927072.56619423.1628666516-347527260.1628666516)の無料版

#手順

###1.ガントチャートで使うコントローラーなどを作成
ターミナルに以下をそのまま打ち込んでください。

```
php artisan make:model Task -m -s
php artisan make:model Link -m
php artisan make:model Gantt -c
```

※末尾の`-m`、`-s`、`-c`は`migrate`、`seeder`、`controller`をそれぞれ一緒に作れという命令です。


###2.ガントチャートを表示するページのルーティング
`web.php`にルーティングを記述します。
今回は`/sample/dhtmlx/gant`にアクセスするとガントチャートが表示されるようにします。

```web.php
use App\Http\Controllers\GanttController;
// （中略）
Route::get('/sample/dhtmlx/gantt', [GantController::class, 'view_gantt'])->name('sample.dhtmlx.gant')
```

###3.ガントチャートを表示するためのコントローラー
`GanttController.php`に以下を記述します。

```GanttController.php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Gantt;

class GanttController extends Controller
{
  // ガントチャートのページを表示
  public function view_gantt()
  {
    return view('sample.dhtmlx.gantt');
  }
}

```

###4.ビューを作成
`/views`内に`/sample/dhtmlx`とフォルダを作成し、その中に`gantt.blade.php`を新規作成し、以下をそのまま記述します。

```gantt.blade.php
<!DOCTYPE html>

<head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    {{-- CDNから読み込み --}}
    <script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
    <link href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css" rel="stylesheet">
    <style>
        html,
        body {
            height: 100%;
            padding: 0px;
            margin: 0px;
            overflow: hidden;
        }

    </style>
</head>

<body>
    <div id="gantt_here" style='width: 100%; height: 100%;'>
        {{-- 描写エリア　ここから --}}

        {{-- 描写エリア　ここまで --}}
    </div>
    <script>
        gantt.config.date_format = "%Y-%m-%d %H:%i:%s"; // オプション：日付フォーマット設定
        gantt.i18n.setLocale("jp"); // オプション：言語設定
        gantt.init("gantt_here"); // 表示　※最低限この行だけあれば表示は可能
    </script>
</body>

```

ちなみに、階層（URL含む）を変更する場合は`GanttController.phpのreturn view(...)の中身`と、`web.phpのget()とname()の中身`を書き換えてください。

###5.ガワが表示されるか確認
`/sample/dhtmlx/gantt`にアクセスして、画像のように表示されれば、ここまでは成功です。
![スクリーンショット 2021-08-12 10.23.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222230/cd7fe19c-fb36-a828-22ae-5f2d728a01da.png)

###6.テーブルを作成
`create_tasks_table.php`に以下を記述します。

```create_tasks_table.php
public function up()
  {
    Schema::create('tasks', function (Blueprint $table) {
      $table->id();
      $table->string('text');
      $table->integer('duration');
      $table->float('progress');
      $table->dateTime('start_date');
      $table->integer('parent');
      $table->timestamps();
    });
  }
```

`create_links_table.php`に以下を記述します。

```create_links_table.php
public function up()
  {
    Schema::create('links', function (Blueprint $table) {
      $table->id();
      $table->string('type');
      $table->integer('source');
      $table->integer('target');
      $table->timestamps();
    });
  }
```

ターミナルに以下を打ち込んでマイグレートと実行します。

```
$ php artisan migrate
```

###7.データベースにテストデータを追加
`TaskSeeder.php`に以下２つを記述します。

```TaskSeeder.php
use App\Models\Task;
```

```TaskSeeder.php
Task::insert([
  [
    'id' => 1, 'text' => 'テスト案件', 'start_date' => '2021-06-01 00:00:00',
    'duration' => 5, 'progress' => 0.8, 'parent' => 0
  ],
  [
    'id' => 2, 'text' => 'テスト作業1', 'start_date' => '2020-06-02 00:00:00',
    'duration' => 4, 'progress' => 0.5, 'parent' => 1
  ],
  [
    'id' => 3, 'text' => 'テスト作業2', 'start_date' => '2020-06-12 00:00:00',
    'duration' => 6, 'progress' => 0.7, 'parent' => 1
  ],
  [
    'id' => 4, 'text' => 'テスト作業3', 'start_date' => '2020-06-18 00:00:00',
    'duration' => 2, 'progress' => 0, 'parent' => 1
  ],
  [
    'id' => 5, 'text' => 'テスト作業1-1', 'start_date' => '2020-07-18 00:00:00',
    'duration' => 5, 'progress' => 0.34, 'parent' => 2
  ],
  [
    'id' => 6, 'text' => 'テスト作業1-2', 'start_date' => '2020-07-20 00:00:00',
    'duration' => 4, 'progress' => 0.5, 'parent' => 2
  ],
  [
    'id' => 7, 'text' => 'テスト作業2-1', 'start_date' => '2020-07-24 00:00:00',
    'duration' => 5, 'progress' => 0.2, 'parent' => 3
  ],
  [
    'id' => 8, 'text' => 'テスト作業2-2', 'start_date' => '2020-07-26 00:00:00',
    'duration' => 4, 'progress' => 0.9, 'parent' => 3
  ]
]);
```

`DatabaseSeeder.php`に以下を記述します。

```DatabaseSeeder.php
public function run()
{
  $this->call(TaskSeeder::class);
}
```

ターミナルで以下を打ち込んでseederを実行します。

```
$ php artisan db:seed
```

###8.データベースから値を取得してJSONで返す処理
`controller/GanttController.php`を開いて以下２つを追加してください。

```GanttController.php
use App\Models\Task;
use App\Models\Link;
```

```GanttController.php
public function get_json()
  {
    $tasks = new Task();
    $links = new Link();

    return response()->json([
      "data" => $tasks->all(),
      "links" => $links->all(),
    ]);
  }
```

###9.api.phpにもAPI用のルーティング
`web.php`ではなく`routes/api.php`です。
`api.php`に以下を追加します。

```api.php
Route::get('/data', [GanttController::class, 'get_json']);
```

###10.ガントチャートが表示されるか確認
画像のように表示されていれば、チャートの表示も成功しました。
![スクリーンショット 2021-08-12 11.59.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222230/75b60c2e-cb99-0cfc-7946-49af906733a0.png)

###11.ガントチャートの保存・変更・削除
ガントチャートは基本的に保存・変更・削除ができないと役に立たないので、この３つの動作を実装します。

まずは`TaskController.php`に以下を追加します。

```TaskController.php

```

`LinkController`に以下を追加します。

```LinkController.php

```

`api.php`に以下を追加します。

```api.php
Route::resources([
  'task' => TaskController::class,
  'link' => LinkController::class,
]);

// または

Route::resource('task', TaskController::class);
Route::resource('link', LinkController::class);
```

`gantt.blade.php`のスクリプトに以下を追加します。

```gantt.blade.php
<script>
  // （前略）
 
  // チャートの保存・変更・削除
  let dp = new gantt.dataProcessor("/api");
  dp.init(gantt);
  dp.setTransactionMode("REST");
</script>
```

###12.保存・変更・削除ができるか確認
`/sample/dhtmlx/gantt`にアクセスし、保存・変更・削除ができるか確認して終了。
Chromeの翻訳機能もDeepL並の精度にならないかな（ﾁﾗﾁﾗ![スクリーンショット 2021-08-12 11.59.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222230/1bc099ac-a2af-f91c-4791-f30b7ba7c580.png)

通知表の英語、最高でも3だったけどDeepLのおかげで海外のサイトもどうにか読めた。

#おまけ
上記スクリプトの`gantt.i18n.setLocale("jp");`の次くらいに以下を追加します。

###タスク名をドラッグ＆ドロップで並び替える

```
// 手動並び替え：有効
gantt.config.order_branch = true;
gantt.config.order_branch_free = true;
```

###行の高さをドラッグで調整する

```
// 行の高さをドラッグで調整：有効
gantt.config.resize_rows = true;
// 行の最小の高さを指定
gantt.config.min_task_grid_row_height = 45;
```

###ドラッグアンドドロップ中に利用可能なドロップ場所を強調表示する

```
gantt.config.order_branch = true;// order tasks only inside a branch
gantt.init("gantt_here");
gantt.parse(demo_tasks);
 
var drag_id = null;
gantt.attachEvent("onRowDragStart", function(id, target, e) {
    drag_id = id;
    return true;
});
gantt.attachEvent("onRowDragEnd", function(id, target) {
    drag_id = null;
    gantt.render();
});
 
gantt.templates.grid_row_class = function(start, end, task){
    if(drag_id && task.id != drag_id){
        if(task.$level != gantt.getTask(drag_id).$level)
            return "cant-drop";
        }
    return "";
};
```