Edited at

neo4j をインストールしてから Cytoscape と組み合わせるまでの話


はじめに

グラフデータベースNeo4jと、図表化JavascriptライブラリCytoscapeを触ってみた時のメモ。

組み合わせた情報で、コードレベルの情報がほとんどないので、誰かの参考になれば嬉しい。

(実際組み合わせてみると、それ単独では記事化するほどの内容ではなかったけど・・・)


Neo4j


インストール


Docker

ほぼこれ


pull

$ docker pull neo4j



お試しだけなので、ホストにマウントはさせないように変更

$ docker run \

--detach \
--publish=7474:7474 --publish=7687:7687 \
--ulimit=nofile=40000:40000 \
neo4j


初期設定

初期画面アクセス時に聞かれたこと ※初期ユーザー(neo4j/neo4j)でログインすると、パスワード変更を求められる。

以降実際とは異なるID/Passwordで記載します。

ID:neo4j

Pass:password

こんな感じのUI

やっぱdockerは環境構築が楽だなぁ・・・


使ってみた

ほぼこれ


ノードの作成/参照/削除

Taroを作る

CREATE (:Person{name:"Taro", age:20});

実行結果が見れる。

1ラベル追加(Personのことか)、1ノード作成(Personラベルに1ノード追加したということだな)、2プロパティ設定(nameとageを設定したってことだろう)、528msで完了。

Codeってタブは別に面白い話無いっぽいな。

Taroを取得する

MATCH (p:Person{name:"Taro"}) RETURN p;

おぉ!Taroが出てきた!

テーブルは、想定通り非構造データ。キーとかどうなってるんだ?もっかいTaro追加するとどうなるんだろう?

もう一度Taroを作って、取得する

CREATE (:Person{name:"Taro", age:20});

MATCH (p:Person{name:"Taro"}) RETURN p;

Taroが二人!キーとかは無いのか???自動生成って理解したほうが良さそうだなー(ドキュメント読めよって言われればそれまでだけど・・・)

全てのTaroを消す

MATCH (p:Person{name:"Taro"}) DELETE p;

Taroを消した。予想通り2ノード消えている。

ちゃんと消えてるか確認する

MATCH (p:Person{name:"Taro"}) RETURN p;

消したので、誰も居ない。


リレーションの作成/参照

ひとまず、A/B/Cの3人を作る

CREATE

(:Person{name:"Alice", age:20}),
(:Person{name:"Bob", age:22}),
(:Person{name:"Carol", age:23});

A/B/Cを作る。できたっぽい。

ちゃんと作れているか確認

MATCH (n) RETURN n;

全ノード取得は、こうやって書くらしい。今更だけど、このSQLっぽいやつは、Cypherと呼ぶらしい。

リレーションの作成

MATCH (p1:Person{name:"Alice"}), (p2:Person{name:"Bob"}) CREATE (p1)-[:Friend]->(p2) ;

関係性の追加はこうするらしい。Carolが寂しそう。

ちなみに、触って動かせる。二人の関係を邪魔することはできない。

よく見たら、Ageとか分からんやんけ、って思った。けど、選択すると、画面下部に詳細情報が表示されるらしい。

ID採番されているやんけ。(なぜCarolは40?ちなみに、Aliceが0、Bobが20だった。20ずつ増える???)

どの属性を表示させるかは選べるらしい。(まぁそうだよね。)ただ、全部は表示できず、どれか一つだけっぽい。


Cytoscape


Cytoscapeを選んだ理由

slideshare - (2017.6.9) Neo4jの可視化ライブラリまとめ

Cytoscapeってのが良さそう。


Cytoscape.js単独での実装

Qiita - Cytoscape.jsを用いてデータを可視化する

※上記を参考にちょっと手直し(jQueryのライブラリを最新化したり、gridを文字列化したりしてる)

最終的にはNeo4jにつなげる必要があるけど、ひとまずは素の状態で触ってみる。


  • 丸いヤツとか、ドラッグアンドドロップできる。

  • マウスのスクロールで拡大・縮小できる。


See the Pen
Cytoscape_test1
by nh321 (@nh321)
on CodePen.

まぁ要素直書きだし、そうだろうな、って感じ。


Neo4jをjavascriptで参照する


シンプルなNeo4j接続

個人ブログ(日本語)見るとnode.jsとかpythonでサーバサイド実装してるみたいだけど、やりたいのはクライアント(ブラウザ)実装。

(DBアクセスのためのID/Passが露出するので、本来はサーバサイドに書くべきだろうけど、今回はお試ししたいだけなので、気にしない。)

よく見ると、CDN経由でライブラリ取得できるみたいなので、それをつかう。

Github - neo4j/neo4j-javascript-driver

基本は、Githubのusageを参考に作成。

そのままだと動かなかったので、stackoverflowも参照。

参考:


neo4jサーバはローカルにあるので、雑なhtmlだけ乗せておく。

<html>

<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/neo4j-driver"></script>
<script>
$(function(){

//https://stackoverflow.com/questions/44205354/unable-to-establish-a-neo4j-bolt-driver-connection-in-javascript
neo4j = neo4j.v1

//driveの設定
var driver = neo4j.driver(
'bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'password')
)

//セッションの生成
var session = driver.session()

// Run a Cypher statement, reading the result in a streaming manner as records arrive:
session
.run('MATCH (person:Person) RETURN person.name AS name')
.subscribe({
onNext: function(record) {
console.log(record.get('name'))
},
onCompleted: function() {
session.close()
driver.close()
},
onError: function(error) {
console.log(error)
}
})
});
</script>
</html>


そうすると、以下のようにF12でconsoleにログ出力されていることを確認。ちゃんと動いている。良かった。

グラフ情報として取得するのはどうやるんだ・・・???


Neo4jからリレーションデータを取得する

誰かのサンプルを見つけたので使ってみる。

Cypherの結論

MATCH (m)-[r]-(n) RETURN m.name as m,type(r) as type,startnode(r).name as start,n.name as n;

Javascripte側はたぶん以下で行けるはず。(sample読んだ雰囲気で理解)

record.get('m');

record.get('type');
record.get('start');
record.get('n');

以下、うごいたhtml/javascript。

<html>

<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/neo4j-driver"></script>
<script>
$(function(){

//https://stackoverflow.com/questions/44205354/unable-to-establish-a-neo4j-bolt-driver-connection-in-javascript
neo4j = neo4j.v1

//driveの設定
var driver = neo4j.driver(
'bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'password')
)

//セッションの生成
var session = driver.session()

// Run a Cypher statement, reading the result in a streaming manner as records arrive:
session
.run('MATCH (m)-[r]-(n) RETURN m.name as m,type(r) as type,startnode(r).name as start,n.name as n')
.subscribe({
onNext: function(record) {
console.log("---");
console.log(record.get('m'));
console.log(record.get('type'));
console.log(record.get('start'));
console.log(record.get('n'));
},
onCompleted: function() {
session.close()
driver.close()
},
onError: function(error) {
console.log(error)
}
})
});
</script>
</html>


Neo4jとCytoscape.jsと組み合わせる

わりとできた。(Cypherは微修正してる)

ソース動いたの確認してから、仲良し3人組に変更してみたけど、ソース修正しなくてもちゃんと動く。いい感じ。

<html>

<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/neo4j-driver"></script>
<script src="http://cdn.bootcss.com/cytoscape/2.3.16/cytoscape.min.js"></script>
<script>
$(function(){

//https://stackoverflow.com/questions/44205354/unable-to-establish-a-neo4j-bolt-driver-connection-in-javascript
neo4j = neo4j.v1

//driveの設定
var driver = neo4j.driver(
'bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'password')
)

//セッションの生成
var session = driver.session()
var style = [];
var elements = {
nodes:[],
edges:[]
};

// Run a Cypher statement, reading the result in a streaming manner as records arrive:
session
.run('MATCH (m:Person)-[r]->(n:Person) RETURN m.name as mname, ID(m) as mid, type(r) as type, n.name as nname, ID(n) as nid')
.subscribe({
onNext: function(record) {
console.log("---");
console.log(record.get('mname'));
console.log(record.get('mid').toString());
console.log(record.get('type'));
console.log(record.get('nname'));
console.log(record.get('nid').toString());

//内容要素はJSONオブジェクトである、サーバ側加工しフロントに渡すもの
elements.nodes.push({
data: {id: record.get('mid').toString(), name: record.get('mname'), label: 'Person'}
})
elements.nodes.push({
data: {id: record.get('nid').toString(), name: record.get('nname'), label: 'Person'}
})
elements.edges.push({
data: {source: record.get('mid').toString(), target: record.get('nid').toString(), relationship: record.get('type')}
})

//内容要素を表現するCSS
var style = [
//セレクターで拾いた内容要素が 指定したCSSを適用する
//ノードの中で、label属性は「Peson」のノードが青色で表示し、文字はname属性を表示する
{ selector: 'node[label = "Person"]',
css: {'background-color': '#6FB1FC', 'content': 'data(name)'}
},
//エッジ全体で、文字はrelationship属性を表示する、終了点での矢印は三角形にする
{ selector: 'edge',
css: {'content': 'data(relationship)', 'target-arrow-shape': 'triangle'}
}
]

//レイアウト設定
var layout = {
//グリッドレイアウトを適用する
name : "grid"
}

// Cytoscapeオブジェクト初期化。
var cy2 = cytoscape({
// containerがHTML内の「cy2」DOM要素に指定
container: document.getElementById('cy2'),
elements: elements,
style: style,
layout: layout,
});
},
onCompleted: function() {
session.close()
driver.close()
},
onError: function(error) {
console.log(error)
}
})
});
</script>

<style type="text/css">
<!--
/* cytoscape graph */
#cy2 {
height: 300px;
width: 400px;
background-color: #f9f9f9;
}
-->
</style>

<div id="cy2"></div>
</html>

最終的にこんな感じになりました。(以下は画像)


さいごに

グラフィカルなツールは触ってて楽しいですねー。

これを使ってジョブの先行後続とか見やすくしたら面白いかなーとか考えます。

以上。