【JavaScript】手軽に綺麗な「要素を矢印で繋いだグラフ」を描く方法（画像付き）

# 概要
JavaScriptでグラフ（ノードとエッジの集合の方）を作りたい。
![スクリーンショット 2020-02-10 16.42.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/be232bb9-a9d1-05ee-ecd4-5da5541fbb29.png)

JavaScriptでグラフを描画できる[d3-graphviz](https://github.com/magjac/d3-graphviz)の使い方や、色変更などできることを調査した。

# 基本の使い方
1.インストールする
簡単な方法は`npm install d3-graphviz`（yarnでも可）
自分でビルドする場合は以下のコードを実行する。

```sh
$ git clone https://github.com/magjac/d3-graphviz.git
$ cd d3-graphviz
$ npm install
$ npm run build
```

また、[unpkg.com](https://unpkg.com/d3-graphviz@2.6.1)を使うこともできる。その際、[d3.js](https://d3js.org/d3.v4.min.js)と[viz.js](https://unpkg.com/viz.js@1.8.1/viz.js)が必要であるためこれらも読み込む。

2.htmlファイルを作成する
必要なjsファイルを読み込み、グラフを描画する要素を用意する。
`<script>`タグ内にグラフ描画スクリプトを記述する。
`renderDot()`の引数に描画したいグラフを記述している。
最も簡単な[デモ](https://bl.ocks.org/magjac/a23d1f1405c2334f288a9cca4c0ef05b)のコードを以下に示す。このコードでは以下のようなグラフが描画される。

```index.html
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/viz.js@1.8.1/viz.js" type="javascript/worker"></script>
<script src="https://unpkg.com/d3-graphviz@2.6.1/build/d3-graphviz.js"></script>
<div id="graph" style="text-align: center;"></div>
<script>

d3.select("#graph").graphviz()
    .fade(false)
    .renderDot('digraph  {a -> b}');

</script>
```
![スクリーンショット 2020-01-21 13.03.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/51194bec-b009-e114-a65c-0615618940ec.png)


以降、グラフ描画スクリプトのみ記述する。

## ノードをカスタマイズする
何も指定せずにグラフを描画すると、上の画像のようなノードになる。
### 形を変更する
ノードの形を変えたい場合は、`shape`を指定する。
グラフのすべてのノードに適用することも、特定のノードのみに適用することも可能。

```js
d3.select("#graph").graphviz()
    .renderDot(`digraph  {
        node [shape="polygon"] // グラフのすべてのノードの形を変える場合
        a [shape="polygon"] // ノード「a」の形を変える場合
        a -> b
    }`);
```
![スクリーンショット 2020-01-21 13.15.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/8858a585-ef96-ed22-a8c2-21ad4fb289e5.png)![スクリーンショット 2020-01-21 13.15.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/056c51ff-abea-5808-7ec8-e8342e5bc91c.png)
[d3-graphviz Shape Tweening Demo](https://bl.ocks.org/magjac/69dc955a2e2ee085f60369c4a73f92a6)に様々な形の例がある。

### 背景の色・透明度を変更する
ノードの背景色や透明度を変更する場合は`fillcolor`を指定する。また、ノードに`style="filled"`を指定しておく（これを指定するとデフォルトのノード背景色はグレーになる）。
透明度は不透明度指定でRGBの後に記述する。ex. #FFFFFF80 // 半透明の白

```js
d3.select("#graph").graphviz()
    .renderDot(`digraph  {
        node [style="filled" fillcolor="#FF000080"] // グラフのすべてのノードの背景色を変える場合
        a [fillcolor="#0000FF80"] // ノード「a」の背景色を変える場合
        b [fillcolor="#FFFF00"] // ノードごとに別の色にすることもできる
        a -> b
    }`);
```
![スクリーンショット 2020-01-21 13.24.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/cf07f26a-2fa8-72a9-a043-4f78b4a01b73.png)![スクリーンショット 2020-01-21 13.25.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/78d53592-8168-c530-ede3-2117871e2ddc.png)![スクリーンショット 2020-01-21 13.26.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/2789ce1a-b158-2868-4a6e-24393acb4b9b.png)


### 枠線の色・透明度・太さを変更する
ノードの枠線の色や透明度を変更する場合は`color`を指定する。透明度指定の方法は背景色と同じである。
また、枠線を太くする場合は`style="bold"`を指定する。

```js
d3.select("#graph").graphviz()
    .renderDot(`digraph  {
        node [color="#FF000080" style="bold"] // グラフのすべてのノードの枠線の色・太さを変える場合。複数指定する場合はスペース区切りで記述する
        a [color="#0000FF80"] // ノード「a」の枠線の色を変える場合
        b [color="#FFFF00"] // ノードごとに別の色にすることもできる
        a -> b
    }`);
```
![スクリーンショット 2020-01-21 13.33.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/ae2f28e8-89e9-8c1c-be19-69a0c3a85670.png)![スクリーンショット 2020-01-21 13.30.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/131a57e5-6345-c642-3568-8edde087106d.png)![スクリーンショット 2020-01-21 13.30.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/192c9e19-1f6d-3a11-cb1d-5d54dbc88851.png)



### 文字の色・透明度・太さを変更する
透明度なしで文字色を変更する場合は`fontcolor`を指定する。透明度や太さを変更する場合は`.attributer()`で変更できる。

```js
d3.select("#graph").graphviz()
    .attributer(function(d) { // 透明度・太さを変更する場合
        if (d.tag == "text") { // <text>タグ
            if (d.parent.attributes.id == "node-a") {
                d3.select(this).attr("font-weight", "bold") // 太字にする
            } else if (d.parent.attributes.id == "node-b") {
                d.attributes.fill = "#00000080" // 半透明の黒にする
            }
        }
    })
    .renderDot(`digraph  {
        node [fontcolor="#FF0000"] // 透明度を指定しない場合の文字色変更
        a [id="node-a"] // 要素のidを指定する
        b [id="node-b"]
        a -> b
    }`);
```
![スクリーンショット 2020-01-21 13.38.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/e1720b45-f563-2487-a7d2-5f5d56c2d77f.png)![スクリーンショット 2020-01-21 13.39.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/48dff67d-8c54-7e17-2099-e0edc6d46791.png)


また、`<ellipse>`タグの時に`fill`に色を指定するとノードの色の変更もできる。

## エッジをカスタマイズする
ノードと同様にエッジの色や太さを変更することもできる。
色・透明度の変更: `color`を指定する。透明度指定はノードの場合と同じである。
太さの変更: `style="bold"`で太くできる。

```js
d3.select("#graph").graphviz()
    .renderDot(`digraph  {
        a -> b [color="#FF000080"] // 色・透明度指定
        a -> c [style="bold"] // 太くする
    }`);
```
![スクリーンショット 2020-01-21 13.44.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/149c0586-0db8-6aae-86d3-af6f732cd586.png)


# その他
## アニメーションさせる
d3-graphvizの機能でグラフをアニメーションをさせることもできる。
[デモ](https://bl.ocks.org/magjac/4acffdb3afbc4f71b448a210b5060bca)を参考にグラフを切り替えるコードを簡易的に記述したものを以下に示す。アニメーションさせたいグラフを配列で用意し、順番に描画している。
以下のコードでは`delay`等を指定していないため、点滅のようになる。

```js
var dotIndex = 0;
var graphviz = d3.select("#graph").graphviz()
    .on("initEnd", render);

function render() {
    var dotLines = dots[dotIndex];
    var dot = dotLines.join('');
    graphviz
        .renderDot(dot)
        .on("end", function () {
            dotIndex = (dotIndex + 1) % dots.length;
            render();
        });
}

var dots = [
    [
        'digraph  {',
        '    a -> b',
        '}'
    ],
    [
        'digraph  {',
        '    a -> b [color="#FF0000"]',
        '}'
    ],
    [
        'digraph  {',
        '    a -> b [color="#0000FF"]',
        '}'
    ],
];
```

## ログを出力する
`.logEvents(true)`でコンソールにログを出力することができる（デフォルトが`true`）。
d3-graphvizの各イベントについて、1つ前のイベントからの経過時間・スタートからの経過時間を知ることができる。

```javascript
d3.select("#graph").graphviz()
    .logEvents(true)
    .renderDot('digraph  {a -> b}');
```
![スクリーンショット 2020-02-10 15.03.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124752/d88566b7-10fa-7683-9133-f421841d150d.png)


# まとめ
- d3-graphvizを用いてJavaScriptで簡単にグラフを作成・表示できる。
- ノード・エッジ共に簡単に色や透明度などを変更できる。
- 複数のグラフをアニメーションで遷移させられることがわかった。