Neo4j|グラフデータのビジュアルツールJSライブラリ

Neo4jのデータベースサーバにはデフォルトでWebブラウザーというアドミン機能があり、ブラウザ内でログイン後にデータベース管理のクエリやデータ取得のクエリを実行することができます。そして、ノードデータのビジュアル表示にも対応しているので、ノード間の関連性を視覚的に理解することができます。しかし、これはNeo4jデータベースが提供している機能なのでこのデータのビジュアル表示を自分のWebアプリのプロジェクトにそのまま適用することができません。

そこで、Javascriptのライブラリを使用すれば自分のプロジェクトでグラフデータをビジュアルで表示することが可能になります。今回は以下のビジュアライゼーションツール2つを試してみることにします。Neo4jのneovis.jsはもうアクティブにメンテナンスはされていいので省略します。

  • Vis-network.js
  • Graphology + Sigma.js
目次

事前準備

AuraDBにNeo4jのインスタンスを作成

今回はAuraDBのインスタンスを使用します。以下の記事を参考にAuraDBのインスタンスを作成し、かつ映画のサンプルデータを作成しておきましょう。

あわせて読みたい
Neo4j|AuraDBにインスタンスを作成してサンプルデータセットを作成する AuraDBとはNeo4jが提供しているNeo4jグラフデータベースのクラウドサービスです。完全マネージドサービスなのでNeo4jデータベースのインストールが不要でかつ保守・運用...

ApacheサーバとPHPのインストール

ApacheとPHPの環境を構築して、AuraDBのインスタンスから取得したグラフデータをブラウザに表示します。以下の記事を参考にしてApacheとPHPのセットアップを行ってください。

あわせて読みたい
Apache(アパッチ)サーバのインストールと設定、そしてHTTPS化 ブログなどをインターネットで公開するには、Webサーバが必要になります。Webサーバの代表的なプログラムにはApacheとNginxの2つがありますが、今回の記事ではApacheの...
あわせて読みたい
PHPをインストールしてコマンドラインとブラウザでPHPを実行する 今回はEC2インスタンス上にPHPをインストールする手順について学習していきます。AWSのEC2インスタンス(Amazon Linux 2023)にSSH接続ができていて、基本的なLinuxコマ...

ComposerでLaudisのPHPドライバーをインストール

PHPからAuraDBのインスタンスに接続する場合、PHP用のドライバーが必要となります。以下の記事を参考にComposerのインストールとNeo4jのPHP用ドライバーをインストールまでを行ってください。

あわせて読みたい
Neo4j|PHPドライバー(Laudis)をインストールしてNeo4jへに接続する Neo4jグラフデータベースに接続する場合は各言語用に用意されたドライバー(ライブラリ)を使用します。公式にサポートされている言語には、Java、.NET、Javascript、Go...

Graphology + Sigma.js

Graphology

https://graphology.github.io
グラフデータの操作・編集用のライブラリで描画機能はない。

sigma.js

https://www.sigmajs.org
Graphologyのデータを元にした描画するためのビジュラライゼーションライブラリ。

NPMコマンドのインストール

NPMコマンドを使用しますので、以下の記事を参考にNode.jsをインストールしてください。

あわせて読みたい
Node.jsのインストールとWebサーバのHTTPS化 Node.jsとはJavascript言語をブラウザ上ではなくサーバ上で動かせるようにしたものです。PHP同様Webサーバがなくてもその言語単体でサーバ上で動作するので、バッチ処理...

graphologyのNodeパッケージをインストール

Nodeプロジェクトを作成し、graphologyとsigmaのパッケージをインストールしていきます。graphology-layout-forceatlas2パッケージのバンドル化にはViteを使用するので、Viteパッケージもインストールしておきます。

【Nodeプロジェクトのフォルダ作成と移動】
mkdir /var/www/sigma-graphology && cd /var/www/sigma-graphology

【プロジェクトの設定ファイルを作成】
npm init -y

【Viteパッケージのインストール】
npm install vite -l --save

【Graphologyパッケージのインストール】
// https://www.npmjs.com/package/graphology
npm install -l --save graphology

【Graphologyの追加ライブラリのインストール】
// https://www.npmjs.com/package/graphology-layout-forceatlas2
npm install -l --save graphology-layout-forceatlas2

【Sigmaパッケージのインストール】
// https://www.npmjs.com/package/sigma
npm install -l --save sigma

Viteの詳細については以下の記事を参考にしてください。

あわせて読みたい
Vite(ヴィート)とNode.jsサーバと連携する Vite(ヴィート)とはフロントエンドのビルドツール(ローカルの開発用サーバ)で、Vue.jsの作者によって開発されました。Viteの言葉自体はフランス語の「素早い」を表...

graphology-layoutのビルド用にViteの設定をする。

ビルドの作成

【プロジェクトフォルダに移動】
cd /var/www/sigma-graphology

【エントリーポイントのファイルの作成】
index-forceatlas2.js
> 以下内容
import forceAtlas2 from 'graphology-layout-forceatlas2';
export { forceAtlas2 };

Viteの設定ファイルの追加

vite.config.js
> 以下内容
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    lib: {
      entry: 'index-forceatlas2.js',
      name: 'GraphologyLayoutForceAtlas2',
      fileName: () => 'graphology-layout-forceatlas2.umd.js',
      formats: ['umd']
    },
    rollupOptions: {
      external: [],
      output: {
        globals: {}
      }
    }
  }
});

package.jsonファイルへの追記

package.jsonファイルに以下の様に”build”の行を追加してください。

package.json
> 以下内容
  "scripts": {
    "build": "vite build",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

ビルドの実行

【ビルドコマンド】
npm run build
> dist/ディレクトリが作成され、その中にgraphology-layout-forceatlas2.umd.jsビルドファイルが作成されます。

【ファイルのコピー】
cp -ip dist/graphology-layout-forceatlas2.umd.js /var/www/html/.

その他必要なライブラリもコピー

以下のライブラリも公開ディレクトリにコピーします。

【graphologyライブラリ】
cp -ip node_modules/graphology/dist/graphology.umd.min.js  /var/www/html/.

【sigmaライブラリ】
cp -ip node_modules/sigma/dist/sigma.min.js /var/www/html/.

サンプルコード

index.php

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

require_once('../lib/vendor/autoload.php');

try {
    //$connection_url = 'bolt+s://neo4j:パスワード@ホスト名';
    //AuraDBのホスト名は以下の様なホスト名になります。
    $connection_url = 'bolt+s://neo4j:パスワード@インスタンスID.databases.neo4j.io';

    $client = Laudis\Neo4j\ClientBuilder::create()
    ->withDriver('bolt+s', $connection_url)
    ->build();

} catch(Exception $e) {
    $err_msg = "Exception: ".$e->getLine()." : ".$e->getMessage();
    var_dump($err_msg);
    exit;
}

try {

    $cypher = "";
    $cypher .= " MATCH (p:Person)-[r]-(m:Movie {title: 'Cloud Atlas'}) ";
    $cypher .= " RETURN p, r, m ";
    $cypher .= " ; ";

    $statement = new Laudis\Neo4j\Databags\Statement($cypher, []);

    $result = $client->runStatement($statement);

} catch (Exception $e) {
    $err_msg = "Exception: ".$e->getLine()." : ".$e->getMessage();
    var_dump($err_msg);
    exit;
}

$nodes = [];
$edges = [];

foreach ($result as $record) {
    $p = $record->get('p');
    $m = $record->get('m');
    $r = $record->get('r');

    foreach ([$p, $m] as $node) {
        $id = $node->getId();
        if (!isset($nodes[$id])) {
            $props = $node->getProperties();
            $label = $props['name'] ?? $props['title'] ?? 'Node';

            $nodes[$id] = [
                'key' => $id,
                'label' => $label,
                // ノード間の距離を縮めるため範囲を狭く
                'x' => rand(0, 15) / 10,
                'y' => rand(0, 15) / 10,
                // ノードの縁を太くする
                'size' => 30, //ノードの大きさ
                'color' => '#888'
            ];
        }
    }

    $edges[] = [
        'key' => uniqid('e'),
        'source' => $r->getStartNodeId(),
        'target' => $r->getEndNodeId(),
        'label' => $r->getType()
    ];
}

$data_json = json_encode(['nodes' => array_values($nodes), 'edges' => $edges]);

?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Graphology + Sigma.js with jQuery</title>

    <!-- Graphology + Layout -->
    <!-- copy from npm dist/ -->
    <script src="/graphology.umd.min.js"></script>
    <!-- bundled by vite -->
    <script src="/graphology-layout-forceatlas2.umd.js"></script>

    <!-- Sigma.js -->
    <!-- copy from npm dist/ -->
    <script src="/sigma.min.js"></script>

    <style>
    html, body {
        margin: 0;
        padding: 0;
        height: 100%;
    }
    #container {
        margin-left: auto;
        margin-right: auto;
        width: 800px;
        height: 600px;
        border: 1px solid lightgray;
    }
    </style>
</head>
<body>
    <div id="container"></div>

    <script>
    const graph = new graphology.Graph();
    const data = JSON.parse('<?php echo addslashes($data_json); ?>');

    data.nodes.forEach(node => {
        graph.addNode(node.key, {
    	    label: node.label,
	    x: node.x,
	    y: node.y,
	    size: node.size,
	    color: node.color
	});
    });

    data.edges.forEach(edge => {
        graph.addEdge(edge.source, edge.target, {
            label: edge.label
        });
    });

    const settings = GraphologyLayoutForceAtlas2.forceAtlas2.inferSettings(graph);
    GraphologyLayoutForceAtlas2.forceAtlas2.assign(graph, {
        settings,
        iterations: 100
    });
    const renderer = new Sigma(graph, document.getElementById('container'));

    // 初期ズームを半分に設定
    /*
    ratio: 1 → 標準スケール
    ratio: 0.5 → ズームイン
    ratio: 2 こっちがズームアウトっぽい
    */
    renderer.getCamera().setState({
        ratio: 2
    });
    </script>
</body>
</html>

表示結果

表示はカスタマイズできますが、ここでは必要最低限に留めています。

vis-network.js (旧vis.js)

サイトURL

https://visjs.org

GitHub URL

https://github.com/visjs/vis-network

サンプルコード

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

require_once('../lib/vendor/autoload.php');

try {
	//$connection_url = 'bolt+s://neo4j:パスワード@ホスト名';
	//AuraDBのホスト名は以下の様なホスト名になります。
	$connection_url = 'bolt+s://neo4j:パスワード@インスタンスID.databases.neo4j.io';

	$client = Laudis\Neo4j\ClientBuilder::create()
	->withDriver('bolt+s', $connection_url)
	->build();

} catch(Exception $e) {
	$err_msg = "Exception: ".$e->getLine()." : ".$e->getMessage();
	var_dump($err_msg);
	exit;
}

try {

	$cypher = "";
	$cypher .= " MATCH (p:Person)-[r]-(m:Movie {title: 'Cloud Atlas'}) ";
	$cypher .= " RETURN p, r, m ";
	$cypher .= " ; ";

	$statement = new Laudis\Neo4j\Databags\Statement($cypher, []);

	$result = $client->runStatement($statement);

} catch (Exception $e) {
	$err_msg = "Exception: ".$e->getLine()." : ".$e->getMessage();
	var_dump($err_msg);
	exit;
}

$nodes = [];
$edges = [];

foreach ($result as $record) {
    $p = $record->get('p');
    $m = $record->get('m');
    $r = $record->get('r');

    foreach ([$p, $m] as $node) {
        $id = (string)$node->getId(); //文字列にキャスト
        if (!isset($nodes[$id])) {
            $props = $node->getProperties();
            $label = $props['name'] ?? $props['title'] ?? 'Node';
            $nodes[$id] = [
                'id' => $id,
                'label' => $label
            ];
        }
    }

    $edges[] = [
        'id' => uniqid('e'),
        'from' => (string)$r->getStartNodeId(),
        'to' => (string)$r->getEndNodeId(),
        'label' => $r->getType()
    ];
}

$data_json = json_encode(['nodes' => array_values($nodes), 'edges' => $edges]);

?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8" />
	<title>Network</title>
	<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
	<style type="text/css">
	#mynetwork {
		margin-left: auto;
		margin-right: auto;
		width: 800px;
		height: 600px;
		border: 1px solid lightgray;
	}
	</style>
</head>
<body>
	<div id="mynetwork"></div>

	<script>
		const response = JSON.parse('<?php echo addslashes($data_json); ?>');
		var nodes = new vis.DataSet(response.nodes);
		var edges = new vis.DataSet(response.edges);

		var container = document.getElementById("mynetwork");
		var data = {
			nodes: nodes,
			edges: edges
		};

		//ノードの横長を円に修正する
		var options = {
			nodes: {
				shape: "dot", // 正円になる
				size: 20,     // サイズを調整
				color: {
					background: '#97C2FC',
					border: '#2B7CE9'
				},
				font: {
					size: 16,
					color: '#000'
				}
			},
			edges: {
				arrows: {
					to: { enabled: true, scaleFactor: 1 }
				},
				font: {
					align: "middle"
				},
				smooth: true
			},
			physics: {
				stabilization: true
			}
		};

		new vis.Network(container, data, options);

	</script>
</body>
</html>

表示結果

こちらも表示はカスタマイズできますが、ここでは必要最低限に留めています。

まとめ

今回はNeo4jノーグラフデータを元に、そのビジュアライゼーションライブラリを2つ試しました。グラフデータはNeo4jのクラウドサービスであるAuraDBからDBのインスタンスを作成してデータを取得しました。そして、ApacheとPHPの環境を用意して、Neo4j用のPHPライブラリもインストールしました。Graphplogy+Sigma.jsを試す際にはNPMパッケージを利用しましたね。加えてViteもインストールしてパッケージのバンドルファイルも作成しました。同様にvis-network.jsでもビジュアル表示を試しました。表示用のカスタマイズオプションは沢山あるので自分でも色々と試してみましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次