Flask + neovis.js で Neo4j のグラフデータを可視化する


# 1. はじめに

　何周回遅れかわからないですが、最近 NoSQL (Mongo, Neo4j) にはまっています！  
　グラフ型 NoSQL データベース Neo4j の勉強を始めて、ちょっと可視化アプリでも作ってみるかと、Python の Flask と 以下の neovis.js を使って作成しようとしたのですが、

* [neo4j-contrib/neovis.js](https://github.com/neo4j-contrib/neovis.js)

neovis.js がメジャーバージョンアップ (v2.0) したらしく、色々と設定項目が変わって Web で使い方を調べるのに苦労したので、ここで紹介します。

先に結果だけ貼ると、Neo4j にある movie データサンプルを neovis.js を使って可視化したものが以下になります（オレンジが映画、紫は人名です）。

![neovis02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/d230c4dd-f8e2-273d-a5f3-b801289e19e6.png)

:::note info
このページでは neovis.js を使って Neo4j のデータを可視化する方法を中心に紹介します。Flask を使ったアプリケーションの作りこみについては行っていません。
:::
 
# 2. Neo4j のセットアップ

　Docker を使ったNeo4j のセットアップ方法を紹介します。すでに Neo4j の実行環境がある方は本章は飛ばしてください。

以下の `docker-compose.yml` ファイルを準備し、

```yml: docker-compose.yml
version: '3'
services:
  neo4j:
    container_name: neo4j
    image: neo4j
    ports:
      - 7474:7474
      - 7687:7687
    volumes: 
      - ./data/data:/data
      - ./data/logs:/logs
      - ./data/conf:/conf
    environment:
      # default username and password "neo4j"
      # neo4j password setting (NEO4J_AUTH=neo4j/<password>)
      - NEO4J_AUTH=neo4j/neo4j_password
```

以下のコマンドで起動します。

```sh
$ docker-compose up -d
```

`http://localhost:7474/` から Neo4j Browser を開くことができるので、`docker-compose.yml` で指定した ユーザ名とパスワードを使用してログインしてください。

:::note info
記載時は、image として neo4j:5.5.0-community を使用しました。
:::

# 3. Flask + neovis.js でのグラフの可視化方法

## 3.1. データセットの準備

　ここから Neo4j に入っているサンプルデータセットを neovis.js で可視化します。

Neo4j Browser から

```
$ :play movies
```

![play_movies.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/758d2cc1-2ed3-3997-6bd0-8339451600e4.png)

と実行し、表示される Chpher スクリプトを叩いて、movie データセットを作成してください。

```
$ MATCH (n) RETURN n
```

で以下のようなグラフが確認できます。

![graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/0710f830-a0d7-0df5-60ee-4232c820339a.png)


## 3.2. Flask + neovis.js での基本の描画

　以下のコマンドで、Python 環境に Flask をインストールします。

```sh
$ pip install flask
```

Flask 用の アプリケーションファイルを準備し、

```python: app.py
from flask import Flask, render_template
app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html') 

if __name__ == '__main__':
    app.run(debug=True)
```

公式にある [advanced-example.html](https://github.com/neo4j-contrib/neovis.js/blob/master/examples/advanced-example.html) を参考にした以下のhtml ファイルを、`templates`  フォルダに作成します。


```html: templates/index.html
<!doctype html>
<!-- https://github.com/neo4j-contrib/neovis.js/blob/master/examples/advanced-example.html -->
<html>

<head>
    <title>Neovis.js Simple Example</title>
    <style type="text/css">
        html,
        body {
            font: 16pt arial;
        }

        #viz {
            width: 800px;
            height: 800px;
            border: 1px solid lightgray;
            font: 22pt arial;
        }
    </style>
    <script src="https://unpkg.com/neovis.js@2.0.2"></script>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"
        integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
</head>

<body onload="draw()">
    <div id="viz"></div>
    Cypher query: <textarea rows="4" cols=50 id="cypher"></textarea><br>
    <input type="submit" value="Submit" id="reload">
    <input type="submit" value="Stabilize" id="stabilize">
</body>

<script type="text/javascript">

    let neoViz;

    function draw() {
        const config = {
            containerId: "viz",
            neo4j: {
                // neo4jの接続設定
                serverUrl: "neo4j://localhost:7687",
                serverUser: "neo4j",
                serverPassword: "neo4j_password",
            },
            visConfig: {
                nodes: {
                    borderWidth: 2,
                    borderWidthSelected: 5,
                },
                edges: {
                    // 線はグレーの矢印にする
                    color: 'gray',
                    arrows: {
                        to: { enabled: true }
                    },
                },
            },
            labels: {
                Person: {
                    label: "name",
                    [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
                        // visのconfig設定を追加する
                        function: {
                            // ツールチップに情報を表示する
                            title: NeoVis.objectToTitleHtml,
                        },
                        static: {
                            // ノードの背景色
                            color: "#beaed4",
                        },
                    }
                },
                Movie: {
                    label: "title",
                    [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
                        function: {
                            // ツールチップに情報を表示する
                            title: NeoVis.objectToTitleHtml,
                        },
                        static: {
                            // ノードの背景色
                            color: "#fdc086",
                            // ノードの形を四角にする
                            shape: 'box',
                        }
                    }
                }
            },
            relationships: {
                ACTED_IN: {
                    [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
                        function: {
                            // ツールチップに情報を表示する
                            title: NeoVis.objectToTitleHtml
                        },
                    }
                },
            },
            // 初期で実行するCypherクエリ
            initialCypher: "MATCH (p:Person)-[r]->(m:Movie) RETURN *"
        };

        viz = new NeoVis.default(config);
        viz.render();
        console.log(viz);
    }

    $('#reload').click(function () {
        // Cypher query に入力されたクエリを読み込んで再描画する
        var cypher = $('#cypher').val();

        if (cypher.length > 3) {
            viz.renderWithCypher(cypher);
        } else {
            console.log('reload');
            viz.reload();

        }

    });

    $('#stabilize').click(function () {
        // うねうねと動く描画を停止する
        viz.stabilize();
    });

</script>
</html>
```

以下のコマンドでアプリケーションを実行し、`http://localhost:5000/` にアクセスしてください。

```sh
$ python app.py 
```

![play_movies_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/87fa0343-9d44-d2b8-90a9-e2c09aec09fc.png)

:::note info
上記図では、ノードの重なりが多いなと感じると思いますが、あとで改善する設定を紹介します。
:::


Cypher query の入力ボックスから、描画を変更することができ、例えばトムハンクスだけを表示するようにすると

```
MATCH (p:Person {name: "Tom Hanks"})-[r]->(m:Movie) RETURN *
```

以下のようになります。

![play_movies_sample2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/a7599b3c-ca1b-8235-8f7b-3fbf011dd467.png)


## 3.3. neovis.js の設定についての補足

　ここからは先ほどの例の補足と、3.2の例では省略した追加の設定項目について紹介します。

### 3.3.1. vis.js について

　neovis.js は vis.js のラッパーなので、多くの設定項目名は  vis.js に由来しているようです。よくわからない点があれば、vis.js で検索するとよいと思います。

* [vis.js のドキュメント](https://almende.github.io/vis/docs/network/)

### 3.3.2. Neo4jとの接続設定

　以下で Neo4j との接続設定を行います。

```yml
neo4j: {
    // neo4jの接続設定
    serverUrl: "neo4j://localhost:7687",
    serverUser: "neo4j",
    serverPassword: "neo4j_password",
},
```

### 3.3.3. ノードと線の設定

　`labels` でノードの設定を、`relationships` で接続線の設定を行います。

　まずノードは以下のように記載します。

```yml
labels: {
    ラベル名: {
        label: (ノードの中央に表示するプロパティ名)
    }
}
```

例えばラベル `Person` の場合は，名前 `name` をノード名として表示してほしいので以下のようになります。

```yml
labels: {
    Person: {
        label: "name"
    }
}
```

また基本の例では表示がうるさくなるので省略したのですが、接続線について、線名を追加したいときは以下のようにします。

```yml
relationships: {
    ACTED_IN: {
        [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
            static: {
                label: "ACTED_IN",
            }
        }
    }
}
```

ACTED_IN の線名を追加すると下記のようになりました。

![play_movies_sample3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/43e237a2-fc96-baba-9353-e6fddc134d01.png)

### 3.3.4. ノードの色とブロックの形の変更

　[公式 のREADME](https://github.com/neo4j-contrib/neovis.js) にあるように、あるラベルのあるプロパティに応じて色を変えたいときは、以下のように `group` にそのプロパティ名を設定します。

```yml
labels: {
    Character: {
        label: "name",
        group: "community",
    }
}
```

一方で、今回は label の種類ごと（Person, Movieなど）に色を指定したかったので、その場合は以下のように `[NeoVis.NEOVIS_ADVANCED_CONFIG]` の `static.color` に色コードを指定します（`color` は `red, blue` などの色名にも対応しています）。
 
```yml
labels: {
    Person: {
        label: "name",
        [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
            static: {
                // ノードの背景色
                color: "#beaed4",
            }
        }
    }
}
```

:::note info
`[NeoVis.NEOVIS_ADVANCED_CONFIG]` が何なのか、詳しい説明を行ったページが見つからなかったのですが、`static` に項目を設定することにより，vis.js で定義されている オプション項目 を直接定数で書き換えられるようです。
* [vis.js のドキュメント](https://almende.github.io/vis/docs/network/nodes.html)
:::

ノードの形状は、`color` と同じように `shape` で変更できます。

```yml
static: {
    // ノードの背景色
    color: "#fdc086",
    // ノードの形を四角にする
    shape: 'box',
}
```

### 3.3.5. ツールチップの表示

　カーソルをあてたときに、ノードや線のプロパティを表示させるには、以下のように ` title: NeoVis.objectToTitleHtml` を追加します。

```yml
[NeoVis.NEOVIS_ADVANCED_CONFIG]: {
    // visのconfig設定を追加する
    function: {
        // ツールチップに情報を表示する
        title: NeoVis.objectToTitleHtml,
    },
}
```

Speed Racer にカーソルをあてると以下のように表示されます。

![play_movies_sample4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/7b2c1d57-c909-ff2d-50a8-e727420da5b6.png)


### 3.3.6. ノードの 配置

　ノードの配置をツリー構造にしたいときは，`visConfig` に以下のような `layout` を追加します。

```yml
visConfig: {
    layout: {
        hierarchical: {
            enabled: true,
            direction: "LR",
            sortMethod: "directed",
            levelSeparation: 300  //階層間の距離（ちょっと広くする）
        }
    },
},
```

![play_movies_sample5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/ee91ca1d-c5dd-7cba-380a-9113bbdc0959.png)

### 3.3.7. ノードの重なりを減らす

　デフォルト設定だと、ノードがたくさんあるときに重なってしまうので、`visConfig` に以下のような `physics` を追加することでノードの間隔を広げます。

```yml
visConfig: {
    edges: {
        smooth: {
            forceDirection: "none"
            }
    },
    physics: {
        barnesHut: {
            gravitationalConstant: -7000,
            springLength: 300,
            // avoidOverlap: 0.5
        },
        minVelocity: 0.75
    }
}
```

:::note info
上記の `physics` の設定については [Playing with Physics](https://visjs.github.io/vis-network/examples/network/physics/physicsConfiguration.html) で、パラメータの数値を変更しながら動作確認することができます。
:::
 
:::note info
`avoidOverlap: 0.5` については入れた方がさらに重ならない気がするのですが、実行時にかなりうねうねとするのでコメントアウトしています。
:::

変更前と変更後で比較を行ったものか以下になります。

**初期設定**

![before](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/80c18715-3220-099b-d7f8-2dbedfe4b238.png)


**変更後**

![after](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3130775/c3e9cb5d-c8ef-a4ed-d3a5-63a207b30ab1.png)

### 3.3.8. コールバック処理

　ノードをクリックしたときに，コールバック処理を行いたいときは以下を追加します。例として、クリックしたノードの情報をコンソールログに出力しました。

```js
// クリックしたノードの情報をコンソールに出すコールバック。
viz.registerOnEvent("completed", (e) => {
    // 処理が完了しないと、viz.network.onがないと言われるので、completedしてから設定する。
    viz.network.on("click", function (params) {
        var ids = params.nodes;
        var clickedNodes = viz.nodes.get(ids);
        console.log('clicked nodes:', clickedNodes);
    });
});
```

## 3.4. 最終的な html ファイル

　前節で色々と紹介した、設定もりもりの html ファイルを以下に貼っておきます。

```html: templates/index.html 
<!doctype html>
<!-- https://github.com/neo4j-contrib/neovis.js/blob/master/examples/advanced-example.html -->
<html>

<head>
    <title>Neovis.js Simple Example</title>
    <style type="text/css">
        html,
        body {
            font: 16pt arial;
        }

        #viz {
            width: 900px;
            height: 800px;
            border: 1px solid lightgray;
            font: 22pt arial;
        }
    </style>
    <script src="https://unpkg.com/neovis.js@2.0.2"></script>
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"
        integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
</head>

<body onload="draw()">
    <div id="viz"></div>
    Cypher query: <textarea rows="4" cols=50 id="cypher"></textarea><br>
    <input type="submit" value="Submit" id="reload">
    <input type="submit" value="Stabilize" id="stabilize">
</body>

<script type="text/javascript">

    let neoViz;

    function draw() {
        const config = {
            containerId: "viz",
            neo4j: {
                // neo4jの接続設定
                serverUrl: "neo4j://localhost:7687",
                serverUser: "neo4j",
                serverPassword: "neo4j_password",
            },
            visConfig: {
                nodes: {
                    borderWidth: 2,
                    borderWidthSelected: 5,
                },
                edges: {
                    // 線はグレーの矢印にする
                    color: 'gray',
                    arrows: {
                        to: { enabled: true }
                    },
                    smooth: {
                        forceDirection: "none"
                        }
                },
                physics: {
                    barnesHut: {
                        gravitationalConstant: -7000,
                        springLength: 300,
                        // avoidOverlap: 0.5
                    },
                    minVelocity: 0.75
                },
                // layout: {
                //     hierarchical: {
                //         enabled: true,
                //         direction: "LR",
                //         sortMethod: "directed",
                //         levelSeparation: 300  //階層間の距離（ちょっと広くする）
                //     }
                // },
            },
            labels: {
                Person: {
                    label: "name",
                    [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
                        // visのconfig設定を追加する
                        function: {
                            // ツールチップに情報を表示する
                            title: NeoVis.objectToTitleHtml,
                        },
                        static: {
                            // ノードの背景色
                            color: "#beaed4",
                        },
                    }
                },
                Movie: {
                    label: "title",
                    [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
                        function: {
                            // ツールチップに情報を表示する
                            title: NeoVis.objectToTitleHtml,
                        },
                        static: {
                            // ノードの背景色
                            color: "#fdc086",
                            // ノードの形を四角にする
                            shape: 'box',
                        }
                    }
                }
            },
            relationships: {
                ACTED_IN: {
                    [NeoVis.NEOVIS_ADVANCED_CONFIG]: {
                        function: {
                            // ツールチップに情報を表示する
                            title: NeoVis.objectToTitleHtml
                        },
                        static: {
                            label: "ACTED_IN",
                            // color: "red"
                        }
                    }
                },
            },
            // 初期で実行するCypherクエリ
            initialCypher: "MATCH (p:Person)-[r]->(m:Movie) RETURN *"
        };

        viz = new NeoVis.default(config);
        viz.render();
        console.log(viz);

        // クリックしたノードの情報をコンソールに出すコールバック。
        viz.registerOnEvent("completed", (e) => {
            // 処理が完了しないと、viz.network.onがないと言われるので、completedしてから設定する。
            viz.network.on("click", function (params) {
                var ids = params.nodes;
                var clickedNodes = viz.nodes.get(ids);
                console.log('clicked nodes:', clickedNodes);
            });
        });
    }

    $('#reload').click(function () {
        // Cypher query に入力されたクエリを読み込んで再描画する
        var cypher = $('#cypher').val();

        if (cypher.length > 3) {
            viz.renderWithCypher(cypher);
        } else {
            console.log('reload');
            viz.reload();

        }

    });

    $('#stabilize').click(function () {
        // うねうねと動く描画を停止する
        viz.stabilize();
    });

</script>
</html>
```

# 4. まとめ

　ここでは neovis.js 基本の設定方法を紹介しました。参考になれば幸いです。

## 4.1. 参考

以下参考にさせていただきました。ありがとうございます。

* [neo4j-contrib/neovis.js](https://github.com/neo4j-contrib/neovis.js)
* [15 Tools for Visualizing Your Neo4j Graph Database](https://neo4j.com/developer-blog/15-tools-for-visualizing-your-neo4j-graph-database/)
* [vis.js Document](https://almende.github.io/vis/docs/network/)
* [neo4jのコンテナを立ち上げるとき追加した設定](https://zenn.dev/nkoguchi_dev/articles/0743e2fd403210)
* [Accessing node data in vis.js click handler](https://stackoverflow.com/questions/35906493/accessing-node-data-in-vis-js-click-handler)
* [Nodes overlap #133](https://github.com/visjs/vis-network/issues/133)