【JointJS】DirectedGraphを利用してツリー形式の図を作成する

JointJSにはDirectedGraphという、有向グラフを作成するためのレイアウト機能が用意されています。今回はそちらを使ってツリー形式の図を作成していきます。

## 完成イメージ

以下のようなツリー形式の図を作成できます。
各四角形の座標は四角形に紐づいたLinkの情報からDirectedGraphが自動計算して決定してくれます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/a1bbd5cb-c6c3-4277-4e54-ba73532c3c67.png)

## 実装

### 下準備

DirectedGraphを使用するためには、`dagre`と`graphlib`というライブラリを追加で取り込む必要があります。お手軽に利用する場合は以下の`<script>`タグを記載すればOKです。

```js
<script src="https://dagrejs.github.io/project/graphlib/latest/graphlib.min.js"></script>
<script src="https://dagrejs.github.io/project/dagre/latest/dagre.min.js"></script>
```

その他、npmでダウンロードする方法等もあります。詳細については各ライブラリのWikiを参照してください。

- [dagreのWiki](https://github.com/dagrejs/dagre/wiki)
- [graphlibのWiki](https://github.com/dagrejs/graphlib/wiki)

### 手順1. 並べるElementをインスタンス化

並べる対象のElementをインスタンス化し、Graphに追加します。

```js
joint.shapes.standard.Rectangle.define('example.MyRect', {
  // 中略
});

const rect1 = joint.shapes.example.MyRect.create('1');
const rect2 = joint.shapes.example.MyRect.create('2');
const rect3 = joint.shapes.example.MyRect.create('3');
const rect4 = joint.shapes.example.MyRect.create('4');
const rect5 = joint.shapes.example.MyRect.create('5');
const rect6 = joint.shapes.example.MyRect.create('6');
const rect7 = joint.shapes.example.MyRect.create('7');
const rect8 = joint.shapes.example.MyRect.create('8');
const rect9 = joint.shapes.example.MyRect.create('9');

const rectangles = [rect1, rect2, rect3, rect4, rect5, rect6, rect7, rect8, rect9];
graph.addCells(rectangles);
```

### 手順2. 並べるElementをLinkでつなぐ

Linkをインスタンス化し、`source`と`target`につなぎたいElementを設定します。

```js
const link1_2 = new joint.shapes.standard.Link({source: rect1, target: rect2});
const link1_3 = new joint.shapes.standard.Link({source: rect1, target: rect3});
const link2_4 = new joint.shapes.standard.Link({source: rect2, target: rect4});
const link2_5 = new joint.shapes.standard.Link({source: rect2, target: rect5});
const link3_6 = new joint.shapes.standard.Link({source: rect3, target: rect6});
const link4_7 = new joint.shapes.standard.Link({source: rect4, target: rect7});
const link4_8 = new joint.shapes.standard.Link({source: rect4, target: rect8});
const link5_9 = new joint.shapes.standard.Link({source: rect5, target: rect9});

const links = [link1_2, link1_3, link2_4, link2_5, link3_6, link4_7, link4_8, link5_9]; 
graph.addCells(links);
```

### 手順3. `layout`メソッドを呼び出す

DirectedGraphの`layout`メソッドを呼び出して整列を実行します。

```js
joint.layout.DirectedGraph.layout(graph, {
    nodeSep: 50,
    edgeSep: 80,
    rankDir: "TB"
});
```

第2引数に指定するオブジェクトには以下のような値を指定できます。

#### `nodeSep`

並列に存在しているElement間の距離を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/9adaf0f5-26a7-8029-5c3d-b562dcbf9861.png)

#### `rankSep`

親子関係にあるElement間の距離を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/4c991cfa-d844-ae21-9b60-f4622f3ba636.png)

#### `marginX` / `marginY`

生成されるDirectedGraphの外側に設けるマージンの長さを指定します。

#### `rankDir`

Elementを整列する方向をしていします。

##### `TB`(top-bottom)を指定した場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/58366c92-7a21-821f-6221-570c2aa0616b.png)

##### `LR`(left-right)を指定した場合

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/ada55dfc-a22a-ecba-6070-c6a4c675e485.png)

#### `ranker`

階層構造を整列する際のアルゴリズムを指定します。
階層数が異なる枝があるときに、長い方に合わせて位置を決定するのか、短い方に合わせて位置を決定するのか、などのアルゴリズムが切り替えられるようになります。

##### `ranker=network-simplex`のときの実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/b6ffbbdd-79d4-3432-4816-2cfa719d6e39.png)

##### `ranker=longest-path`のときの実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129526/87b38db0-3048-48ba-d58d-223e94b21d6b.png)

`network-simplex`の結果と比べると`3`,`6`の位置がズレています。



## DirectedGraphを使うメリット・デメリット

私なりに考えるDirectedGraphのメリット・デメリットは以下となります。

### メリット

- 座標の計算を行わずにお手軽にElementを整列できる
- 線の種類や向きはある程度は指定可能
- Elementが増えた場合にも対応できる

### デメリット[^1]

- 細かい線の位置指定はできない
- オプション以上のカスタマイズができない

## まとめ

今回は有向グラフを作成できる`DirectedGraph`の機能を紹介しました。お手軽にツリー構造の図を作成できるため、簡単な要件であれば有用性の高い機能です。ツリー構造を作成する際は利用を検討してみてください。

※この記事は [JointJS Advent Calendar 2023](https://qiita.com/advent-calendar/2023/jointjs) の記事です。他の記事を読む場合はカレンダーのページを参照してください。

https://qiita.com/advent-calendar/2023/jointjs

[^1]: 私の開発プロジェクトではデメリットのカスタマイズ性の乏しさが仇となり、DirectedGraphの使用を見送りにしました。